From 460884fea323e8227ceed7e8c8f8b80773324097 Mon Sep 17 00:00:00 2001 From: Wendell Jones Date: Thu, 19 Feb 2026 07:36:25 -0500 Subject: [PATCH] repo creation with initial code after cloning public repo --- .idea/.idea.Jellyfin/.idea/encodings.xml | 4 + BannedSymbols.txt | 4 + CONTRIBUTORS.md | 290 + Directory.Build.props | 27 + Directory.Packages.props | 90 + Emby.Naming/Audio/AlbumParser.cs | 70 + Emby.Naming/Audio/AudioFileParser.cs | 25 + Emby.Naming/AudioBook/AudioBookFileInfo.cs | 77 + .../AudioBook/AudioBookFilePathParser.cs | 67 + .../AudioBookFilePathParserResult.cs | 18 + Emby.Naming/AudioBook/AudioBookInfo.cs | 56 + .../AudioBook/AudioBookListResolver.cs | 150 + Emby.Naming/AudioBook/AudioBookNameParser.cs | 67 + .../AudioBook/AudioBookNameParserResult.cs | 18 + Emby.Naming/AudioBook/AudioBookResolver.cs | 56 + Emby.Naming/Book/BookFileNameParser.cs | 75 + Emby.Naming/Book/BookFileNameParserResult.cs | 41 + Emby.Naming/Common/EpisodeExpression.cs | 70 + Emby.Naming/Common/MediaType.cs | 23 + Emby.Naming/Common/NamingOptions.cs | 913 + Emby.Naming/Emby.Naming.csproj | 59 + .../ExternalFiles/ExternalPathParser.cs | 133 + .../ExternalFiles/ExternalPathParserResult.cs | 59 + Emby.Naming/Properties/AssemblyInfo.cs | 21 + Emby.Naming/TV/EpisodeInfo.cs | 94 + Emby.Naming/TV/EpisodePathParser.cs | 242 + Emby.Naming/TV/EpisodePathParserResult.cs | 54 + Emby.Naming/TV/EpisodeResolver.cs | 93 + Emby.Naming/TV/SeasonPathParser.cs | 174 + Emby.Naming/TV/SeasonPathParserResult.cs | 26 + Emby.Naming/TV/SeriesInfo.cs | 29 + Emby.Naming/TV/SeriesPathParser.cs | 60 + Emby.Naming/TV/SeriesPathParserResult.cs | 19 + Emby.Naming/TV/SeriesResolver.cs | 71 + Emby.Naming/TV/TvParserHelpers.cs | 44 + Emby.Naming/Video/CleanDateTimeParser.cs | 55 + Emby.Naming/Video/CleanDateTimeResult.cs | 31 + Emby.Naming/Video/CleanStringParser.cs | 55 + Emby.Naming/Video/ExtraResult.cs | 22 + Emby.Naming/Video/ExtraRule.cs | 46 + Emby.Naming/Video/ExtraRuleResolver.cs | 69 + Emby.Naming/Video/ExtraRuleType.cs | 28 + Emby.Naming/Video/FileStack.cs | 56 + Emby.Naming/Video/FileStackRule.cs | 48 + Emby.Naming/Video/Format3DParser.cs | 81 + Emby.Naming/Video/Format3DResult.cs | 31 + Emby.Naming/Video/Format3DRule.cs | 31 + Emby.Naming/Video/StackResolver.cs | 156 + Emby.Naming/Video/StubResolver.cs | 50 + Emby.Naming/Video/StubTypeRule.cs | 31 + Emby.Naming/Video/VideoFileInfo.cs | 120 + Emby.Naming/Video/VideoInfo.cs | 53 + Emby.Naming/Video/VideoListResolver.cs | 223 + Emby.Naming/Video/VideoResolver.cs | 160 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../Debug/net10.0/Emby.Naming.assets.cache | Bin 0 -> 14005 bytes ...Emby.Naming.csproj.AssemblyReference.cache | Bin 0 -> 3805 bytes .../obj/Emby.Naming.csproj.nuget.dgspec.json | 3330 +++ .../obj/Emby.Naming.csproj.nuget.g.props | 26 + .../obj/Emby.Naming.csproj.nuget.g.targets | 8 + Emby.Naming/obj/project.assets.json | 1642 ++ Emby.Naming/obj/project.nuget.cache | 34 + Emby.Naming/obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + Emby.Naming/obj/rider.project.restore.info | 1 + Emby.Photos/Emby.Photos.csproj | 42 + Emby.Photos/PhotoProvider.cs | 175 + Emby.Photos/Properties/AssemblyInfo.cs | 21 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../Debug/net10.0/Emby.Photos.assets.cache | Bin 0 -> 17212 bytes ...Emby.Photos.csproj.AssemblyReference.cache | Bin 0 -> 4878 bytes .../obj/Emby.Photos.csproj.nuget.dgspec.json | 4754 +++++ .../obj/Emby.Photos.csproj.nuget.g.props | 26 + .../obj/Emby.Photos.csproj.nuget.g.targets | 9 + Emby.Photos/obj/project.assets.json | 1911 ++ Emby.Photos/obj/project.nuget.cache | 39 + Emby.Photos/obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + Emby.Photos/obj/rider.project.restore.info | 1 + .../AppBase/BaseApplicationPaths.cs | 133 + .../AppBase/BaseConfigurationManager.cs | 383 + .../AppBase/ConfigurationHelper.cs | 62 + .../ApplicationHost.cs | 1014 + .../Branding/BrandingConfigurationFactory.cs | 25 + .../Chapters/ChapterManager.cs | 300 + .../Collections/CollectionImageProvider.cs | 96 + .../Collections/CollectionManager.cs | 369 + .../ServerConfigurationManager.cs | 123 + .../ConfigurationOptions.cs | 27 + .../Cryptography/CryptographyProvider.cs | 101 + .../Data/CleanDatabaseScheduledTask.cs | 119 + .../Data/ItemTypeLookup.cs | 64 + .../Data/SqliteExtensions.cs | 271 + .../Data/TypeMapper.cs | 33 + .../Devices/DeviceId.cs | 98 + Emby.Server.Implementations/Dto/DtoService.cs | 1511 ++ .../Emby.Server.Implementations.csproj | 76 + .../EntryPoints/LibraryChangedNotifier.cs | 402 + .../EntryPoints/UserDataChangeNotifier.cs | 167 + .../HttpServer/Security/AuthService.cs | 43 + .../HttpServer/WebSocketConnection.cs | 283 + .../HttpServer/WebSocketManager.cs | 98 + .../IO/FileRefresher.cs | 215 + .../IO/LibraryMonitor.cs | 491 + .../IO/ManagedFileSystem.cs | 716 + .../IO/MbLinkShortcutHandler.cs | 35 + .../IStartupOptions.cs | 28 + .../Images/ArtistImageProvider.cs | 46 + .../Images/BaseDynamicImageProvider.cs | 325 + .../Images/BaseFolderImageProvider.cs | 66 + .../Images/CollectionFolderImageProvider.cs | 108 + .../Images/DynamicImageProvider.cs | 137 + .../Images/FolderImageProvider.cs | 35 + .../Images/GenreImageProvider.cs | 52 + .../Images/MusicAlbumImageProvider.cs | 30 + .../Images/MusicGenreImageProvider.cs | 60 + .../Images/PhotoAlbumImageProvider.cs | 19 + .../Images/PlaylistImageProvider.cs | 65 + .../Library/CoreResolutionIgnoreRule.cs | 69 + .../Library/DotIgnoreIgnoreRule.cs | 140 + .../Library/ExternalDataManager.cs | 77 + .../Library/IgnorePatterns.cs | 136 + .../Library/KeyframeManager.cs | 44 + .../Library/LibraryManager.cs | 3389 ++++ .../Library/LiveStreamHelper.cs | 191 + .../Library/MediaSourceManager.cs | 924 + .../Library/MediaStreamSelector.cs | 193 + .../Library/MusicManager.cs | 135 + .../Library/PathExtensions.cs | 204 + .../Library/PathManager.cs | 101 + .../Library/ResolverHelper.cs | 168 + .../Library/Resolvers/Audio/AudioResolver.cs | 240 + .../Resolvers/Audio/MusicAlbumResolver.cs | 181 + .../Resolvers/Audio/MusicArtistResolver.cs | 122 + .../Library/Resolvers/BaseVideoResolver.cs | 289 + .../Library/Resolvers/Books/BookResolver.cs | 86 + .../Library/Resolvers/ExtraResolver.cs | 104 + .../Library/Resolvers/FolderResolver.cs | 35 + .../Resolvers/GenericFolderResolver.cs | 28 + .../Library/Resolvers/GenericVideoResolver.cs | 28 + .../Resolvers/Movies/BoxSetResolver.cs | 74 + .../Library/Resolvers/Movies/MovieResolver.cs | 586 + .../Library/Resolvers/PhotoAlbumResolver.cs | 95 + .../Library/Resolvers/PhotoResolver.cs | 122 + .../Library/Resolvers/PlaylistResolver.cs | 82 + .../Resolvers/SpecialFolderResolver.cs | 86 + .../Library/Resolvers/TV/EpisodeResolver.cs | 90 + .../Library/Resolvers/TV/SeasonResolver.cs | 100 + .../Library/Resolvers/TV/SeriesResolver.cs | 211 + .../Library/SearchEngine.cs | 200 + .../Library/SplashscreenPostScanTask.cs | 91 + .../Library/UserDataManager.cs | 368 + .../Library/UserViewManager.cs | 402 + .../Library/Validators/ArtistsPostScanTask.cs | 48 + .../Library/Validators/ArtistsValidator.cs | 110 + .../Validators/CollectionPostScanTask.cs | 153 + .../Library/Validators/GenresPostScanTask.cs | 48 + .../Library/Validators/GenresValidator.cs | 103 + .../Validators/MusicGenresPostScanTask.cs | 48 + .../Validators/MusicGenresValidator.cs | 80 + .../Library/Validators/PeopleValidator.cs | 120 + .../Library/Validators/StudiosPostScanTask.cs | 49 + .../Library/Validators/StudiosValidator.cs | 104 + .../Localization/Core/ab.json | 3 + .../Localization/Core/af.json | 139 + .../Localization/Core/ar.json | 141 + .../Localization/Core/as.json | 43 + .../Localization/Core/be.json | 141 + .../Localization/Core/bg-BG.json | 141 + .../Localization/Core/bn.json | 141 + .../Localization/Core/ca.json | 141 + .../Localization/Core/chr.json | 52 + .../Localization/Core/cs.json | 141 + .../Localization/Core/cy.json | 136 + .../Localization/Core/da.json | 141 + .../Localization/Core/de.json | 141 + .../Localization/Core/el.json | 141 + .../Localization/Core/en-GB.json | 141 + .../Localization/Core/en-US.json | 141 + .../Localization/Core/enm.json | 1 + .../Localization/Core/eo.json | 130 + .../Localization/Core/es-AR.json | 141 + .../Localization/Core/es-MX.json | 141 + .../Localization/Core/es.json | 141 + .../Localization/Core/es_419.json | 141 + .../Localization/Core/es_DO.json | 135 + .../Localization/Core/et.json | 141 + .../Localization/Core/eu.json | 141 + .../Localization/Core/fa.json | 139 + .../Localization/Core/fi.json | 141 + .../Localization/Core/fil.json | 129 + .../Localization/Core/fo.json | 18 + .../Localization/Core/fr-CA.json | 141 + .../Localization/Core/fr.json | 141 + .../Localization/Core/ga.json | 141 + .../Localization/Core/gl.json | 141 + .../Localization/Core/gsw.json | 129 + .../Localization/Core/he.json | 141 + .../Localization/Core/he_IL.json | 1 + .../Localization/Core/hi.json | 140 + .../Localization/Core/hr.json | 141 + .../Localization/Core/ht.json | 62 + .../Localization/Core/hu.json | 141 + .../Localization/Core/hy.json | 39 + .../Localization/Core/id.json | 141 + .../Localization/Core/is.json | 138 + .../Localization/Core/it.json | 141 + .../Localization/Core/ja.json | 141 + .../Localization/Core/jbo.json | 7 + .../Localization/Core/ka.json | 129 + .../Localization/Core/kab.json | 14 + .../Localization/Core/kk.json | 129 + .../Localization/Core/km.json | 133 + .../Localization/Core/kn.json | 135 + .../Localization/Core/ko.json | 141 + .../Localization/Core/kw.json | 139 + .../Localization/Core/ky.json | 1 + .../Localization/Core/lb.json | 139 + .../Localization/Core/lt-LT.json | 141 + .../Localization/Core/lv.json | 141 + .../Localization/Core/lzh.json | 11 + .../Localization/Core/mi.json | 9 + .../Localization/Core/mk.json | 136 + .../Localization/Core/ml.json | 133 + .../Localization/Core/mn.json | 141 + .../Localization/Core/mr.json | 141 + .../Localization/Core/ms.json | 141 + .../Localization/Core/mt.json | 139 + .../Localization/Core/my.json | 132 + .../Localization/Core/nb.json | 141 + .../Localization/Core/ne.json | 127 + .../Localization/Core/nl.json | 141 + .../Localization/Core/nn.json | 125 + .../Localization/Core/oc.json | 1 + .../Localization/Core/or.json | 12 + .../Localization/Core/pa.json | 141 + .../Localization/Core/pl.json | 141 + .../Localization/Core/pr.json | 122 + .../Localization/Core/pt-BR.json | 141 + .../Localization/Core/pt-PT.json | 141 + .../Localization/Core/pt.json | 141 + .../Localization/Core/ro.json | 141 + .../Localization/Core/ru.json | 141 + .../Localization/Core/si.json | 1 + .../Localization/Core/sk.json | 141 + .../Localization/Core/sl-SI.json | 141 + .../Localization/Core/sn.json | 28 + .../Localization/Core/sq.json | 141 + .../Localization/Core/sr.json | 141 + .../Localization/Core/sv.json | 141 + .../Localization/Core/sw.json | 1 + .../Localization/Core/ta.json | 141 + .../Localization/Core/te.json | 64 + .../Localization/Core/th.json | 141 + .../Localization/Core/tr.json | 141 + .../Localization/Core/ug.json | 124 + .../Localization/Core/uk.json | 141 + .../Localization/Core/ur.json | 16 + .../Localization/Core/ur_PK.json | 131 + .../Localization/Core/uz.json | 115 + .../Localization/Core/vi.json | 141 + .../Localization/Core/zh-CN.json | 141 + .../Localization/Core/zh-HK.json | 141 + .../Localization/Core/zh-TW.json | 141 + .../Localization/Core/zu.json | 38 + .../Localization/LocalizationManager.cs | 562 + .../Localization/Ratings/0-prefer.json | 34 + .../Localization/Ratings/ar.json | 41 + .../Localization/Ratings/au.json | 69 + .../Localization/Ratings/be.json | 55 + .../Localization/Ratings/bg.json | 34 + .../Localization/Ratings/br.json | 55 + .../Localization/Ratings/ca.json | 90 + .../Localization/Ratings/cl.json | 41 + .../Localization/Ratings/co.json | 55 + .../Localization/Ratings/cz.json | 34 + .../Localization/Ratings/de.json | 41 + .../Localization/Ratings/dk.json | 48 + .../Localization/Ratings/es.json | 90 + .../Localization/Ratings/fi.json | 48 + .../Localization/Ratings/fr.json | 69 + .../Localization/Ratings/gb.json | 97 + .../Localization/Ratings/gr.json | 34 + .../Localization/Ratings/hu.json | 41 + .../Localization/Ratings/id.json | 34 + .../Localization/Ratings/ie.json | 55 + .../Localization/Ratings/in.json | 55 + .../Localization/Ratings/it.json | 34 + .../Localization/Ratings/jp.json | 62 + .../Localization/Ratings/kr.json | 41 + .../Localization/Ratings/kz.json | 41 + .../Localization/Ratings/lt.json | 41 + .../Localization/Ratings/mx.json | 41 + .../Localization/Ratings/nl.json | 55 + .../Localization/Ratings/no.json | 69 + .../Localization/Ratings/nz.json | 76 + .../Localization/Ratings/ph.json | 48 + .../Localization/Ratings/pl.json | 41 + .../Localization/Ratings/pt.json | 62 + .../Localization/Ratings/ro.json | 48 + .../Localization/Ratings/ru.json | 48 + .../Localization/Ratings/se.json | 55 + .../Localization/Ratings/sg.json | 48 + .../Localization/Ratings/sk.json | 41 + .../Localization/Ratings/th.json | 48 + .../Localization/Ratings/tr.json | 69 + .../Localization/Ratings/tw.json | 41 + .../Localization/Ratings/ua.json | 34 + .../Localization/Ratings/uk.json | 97 + .../Localization/Ratings/us.json | 83 + .../Localization/Ratings/za.json | 55 + .../Localization/countries.json | 836 + .../Localization/iso6392.txt | 496 + .../Playlists/PlaylistManager.cs | 688 + .../Playlists/PlaylistsFolder.cs | 55 + .../Plugins/PluginLoadContext.cs | 33 + .../Plugins/PluginManager.cs | 913 + .../Properties/AssemblyInfo.cs | 24 + .../QuickConnect/QuickConnectManager.cs | 231 + .../ScheduledTasks/ScheduledTaskWorker.cs | 678 + .../ScheduledTasks/TaskManager.cs | 263 + .../Tasks/AudioNormalizationTask.cs | 295 + .../ScheduledTasks/Tasks/ChapterImagesTask.cs | 168 + .../Tasks/CleanActivityLogTask.cs | 76 + .../CleanupCollectionAndPlaylistPathsTask.cs | 139 + .../Tasks/CleanupUserDataTask.cs | 77 + .../Tasks/DeleteCacheFileTask.cs | 143 + .../ScheduledTasks/Tasks/DeleteLogFileTask.cs | 101 + .../Tasks/DeleteTranscodeFileTask.cs | 124 + .../Tasks/MediaSegmentExtractionTask.cs | 119 + .../Tasks/OptimizeDatabaseTask.cs | 82 + .../Tasks/PeopleValidationTask.cs | 129 + .../ScheduledTasks/Tasks/PluginUpdateTask.cs | 124 + .../Tasks/RefreshMediaLibraryTask.cs | 65 + .../ScheduledTasks/Triggers/DailyTrigger.cs | 86 + .../Triggers/IntervalTrigger.cs | 106 + .../ScheduledTasks/Triggers/StartupTrigger.cs | 53 + .../ScheduledTasks/Triggers/WeeklyTrigger.cs | 109 + .../Serialization/MyXmlSerializer.cs | 108 + .../ServerApplicationPaths.cs | 107 + .../Session/SessionManager.cs | 2154 ++ .../Session/SessionWebSocketListener.cs | 268 + .../Session/WebSocketController.cs | 186 + .../Sorting/AiredEpisodeOrderComparer.cs | 160 + .../Sorting/AlbumArtistComparer.cs | 42 + .../Sorting/AlbumComparer.cs | 42 + .../Sorting/ArtistComparer.cs | 39 + .../Sorting/CommunityRatingComparer.cs | 34 + .../Sorting/CriticRatingComparer.cs | 35 + .../Sorting/DateCreatedComparer.cs | 35 + .../Sorting/DateLastMediaAddedComparer.cs | 69 + .../Sorting/DatePlayedComparer.cs | 70 + .../Sorting/IndexNumberComparer.cs | 50 + .../Sorting/IsFavoriteOrLikeComparer.cs | 59 + .../Sorting/IsFolderComparer.cs | 39 + .../Sorting/IsPlayedComparer.cs | 60 + .../Sorting/IsUnplayedComparer.cs | 60 + .../Sorting/NameComparer.cs | 35 + .../Sorting/OfficialRatingComparer.cs | 54 + .../Sorting/ParentIndexNumberComparer.cs | 50 + .../Sorting/PlayCountComparer.cs | 64 + .../Sorting/PremiereDateComparer.cs | 63 + .../Sorting/ProductionYearComparer.cs | 55 + .../Sorting/RandomComparer.cs | 31 + .../Sorting/RuntimeComparer.cs | 34 + .../Sorting/SeriesSortNameComparer.cs | 36 + .../Sorting/SortNameComparer.cs | 34 + .../Sorting/StartDateComparer.cs | 45 + .../Sorting/StudioComparer.cs | 34 + Emby.Server.Implementations/SyncPlay/Group.cs | 679 + .../SyncPlay/SyncPlayManager.cs | 419 + Emby.Server.Implementations/SystemManager.cs | 140 + .../TV/TVSeriesManager.cs | 280 + .../Updates/InstallationManager.cs | 584 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../Emby.Server.Implementations.assets.cache | Bin 0 -> 47770 bytes ...ementations.csproj.AssemblyReference.cache | Bin 0 -> 20506 bytes ...r.Implementations.csproj.nuget.dgspec.json | 10134 +++++++++ ...erver.Implementations.csproj.nuget.g.props | 26 + ...ver.Implementations.csproj.nuget.g.targets | 10 + .../obj/project.assets.json | 4303 ++++ .../obj/project.nuget.cache | 86 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../Attributes/AcceptsFileAttribute.cs | 29 + .../Attributes/AcceptsImageFileAttribute.cs | 17 + .../Attributes/ParameterObsoleteAttribute.cs | 11 + .../Attributes/ProducesAudioFileAttribute.cs | 17 + .../Attributes/ProducesFileAttribute.cs | 29 + .../Attributes/ProducesImageFileAttribute.cs | 17 + .../ProducesPlaylistFileAttribute.cs | 17 + .../Attributes/ProducesVideoFileAttribute.cs | 17 + .../AnonymousLanAccessHandler.cs | 48 + .../AnonymousLanAccessRequirement.cs | 11 + .../Auth/CustomAuthenticationHandler.cs | 90 + .../DefaultAuthorizationHandler.cs | 96 + .../DefaultAuthorizationRequirement.cs | 24 + .../FirstTimeSetupHandler.cs | 56 + .../FirstTimeSetupRequirement.cs | 25 + .../LocalAccessOrRequiresElevationHandler.cs | 56 + ...calAccessOrRequiresElevationRequirement.cs | 11 + .../SyncPlayAccessHandler.cs | 77 + .../SyncPlayAccessRequirement.cs | 25 + .../UserPermissionHandler.cs | 56 + .../UserPermissionRequirement.cs | 26 + Jellyfin.Api/BaseJellyfinApiController.cs | 37 + .../Constants/AuthenticationSchemes.cs | 12 + Jellyfin.Api/Constants/InternalClaimTypes.cs | 42 + Jellyfin.Api/Constants/UserRoles.cs | 22 + .../Controllers/ActivityLogController.cs | 118 + Jellyfin.Api/Controllers/ApiKeyController.cs | 76 + Jellyfin.Api/Controllers/ArtistsController.cs | 481 + Jellyfin.Api/Controllers/AudioController.cs | 361 + Jellyfin.Api/Controllers/BackupController.cs | 127 + .../Controllers/BrandingController.cs | 65 + .../Controllers/ChannelsController.cs | 254 + .../Controllers/ClientLogController.cs | 78 + .../Controllers/CollectionController.cs | 111 + .../Controllers/ConfigurationController.cs | 144 + .../Controllers/DashboardController.cs | 115 + Jellyfin.Api/Controllers/DevicesController.cs | 140 + .../DisplayPreferencesController.cs | 222 + .../Controllers/DynamicHlsController.cs | 2081 ++ .../Controllers/EnvironmentController.cs | 182 + Jellyfin.Api/Controllers/FilterController.cs | 218 + Jellyfin.Api/Controllers/GenresController.cs | 208 + .../Controllers/HlsSegmentController.cs | 191 + Jellyfin.Api/Controllers/ImageController.cs | 2069 ++ .../Controllers/InstantMixController.cs | 403 + .../Controllers/ItemLookupController.cs | 282 + .../Controllers/ItemRefreshController.cs | 93 + .../Controllers/ItemUpdateController.cs | 544 + Jellyfin.Api/Controllers/ItemsController.cs | 1072 + Jellyfin.Api/Controllers/LibraryController.cs | 1057 + .../Controllers/LibraryStructureController.cs | 359 + Jellyfin.Api/Controllers/LiveTvController.cs | 1199 ++ .../Controllers/LocalizationController.cs | 84 + Jellyfin.Api/Controllers/LyricsController.cs | 245 + .../Controllers/MediaInfoController.cs | 344 + .../Controllers/MediaSegmentsController.cs | 62 + Jellyfin.Api/Controllers/MoviesController.cs | 326 + .../Controllers/MusicGenresController.cs | 204 + Jellyfin.Api/Controllers/PackageController.cs | 164 + Jellyfin.Api/Controllers/PersonsController.cs | 139 + .../Controllers/PlaylistsController.cs | 568 + .../Controllers/PlaystateController.cs | 535 + Jellyfin.Api/Controllers/PluginsController.cs | 263 + .../Controllers/QuickConnectController.cs | 119 + .../Controllers/RemoteImageController.cs | 182 + .../Controllers/ScheduledTasksController.cs | 161 + Jellyfin.Api/Controllers/SearchController.cs | 266 + Jellyfin.Api/Controllers/SessionController.cs | 453 + Jellyfin.Api/Controllers/StartupController.cs | 154 + Jellyfin.Api/Controllers/StudiosController.cs | 156 + .../Controllers/SubtitleController.cs | 580 + .../Controllers/SuggestionsController.cs | 125 + .../Controllers/SyncPlayController.cs | 440 + Jellyfin.Api/Controllers/SystemController.cs | 228 + .../Controllers/TimeSyncController.cs | 33 + .../Controllers/TrailersController.cs | 298 + .../Controllers/TrickplayController.cs | 105 + Jellyfin.Api/Controllers/TvShowsController.cs | 387 + .../Controllers/UniversalAudioController.cs | 358 + Jellyfin.Api/Controllers/UserController.cs | 636 + .../Controllers/UserLibraryController.cs | 696 + .../Controllers/UserViewsController.cs | 170 + .../Controllers/VideoAttachmentsController.cs | 84 + Jellyfin.Api/Controllers/VideosController.cs | 658 + Jellyfin.Api/Controllers/YearsController.cs | 227 + .../Extensions/ClaimsPrincipalExtensions.cs | 80 + Jellyfin.Api/Extensions/DtoExtensions.cs | 51 + .../CamelCaseJsonProfileFormatter.cs | 20 + Jellyfin.Api/Formatters/CssOutputFormatter.cs | 19 + .../PascalCaseJsonProfileFormatter.cs | 23 + Jellyfin.Api/Formatters/XmlOutputFormatter.cs | 42 + Jellyfin.Api/Helpers/AudioHelper.cs | 157 + Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 920 + .../Helpers/FileStreamResponseHelpers.cs | 169 + Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs | 349 + Jellyfin.Api/Helpers/HlsHelpers.cs | 132 + Jellyfin.Api/Helpers/MediaInfoHelper.cs | 518 + Jellyfin.Api/Helpers/RequestHelpers.cs | 188 + Jellyfin.Api/Helpers/StreamingHelpers.cs | 607 + Jellyfin.Api/Jellyfin.Api.csproj | 48 + .../BaseUrlRedirectionMiddleware.cs | 78 + .../Middleware/ExceptionMiddleware.cs | 150 + .../IpBasedAccessValidationMiddleware.cs | 63 + .../QueryStringDecodingMiddleware.cs | 38 + .../Middleware/ResponseTimeMiddleware.cs | 68 + .../Middleware/RobotsRedirectionMiddleware.cs | 45 + .../ServerStartupMessageMiddleware.cs | 50 + .../Middleware/UrlDecodeQueryFeature.cs | 83 + .../Middleware/WebSocketHandlerMiddleware.cs | 39 + .../CommaDelimitedCollectionModelBinder.cs | 89 + .../ModelBinders/LegacyDateTimeModelBinder.cs | 48 + .../ModelBinders/NullableEnumModelBinder.cs | 47 + .../NullableEnumModelBinderProvider.cs | 26 + .../PipeDelimitedCollectionModelBinder.cs | 89 + .../ClientLogDocumentResponseDto.cs | 21 + Jellyfin.Api/Models/ConfigurationPageInfo.cs | 66 + .../DefaultDirectoryBrowserInfoDto.cs | 12 + .../Models/EnvironmentDtos/ValidatePathDto.cs | 22 + .../LibraryDtos/LibraryOptionInfoDto.cs | 17 + .../LibraryDtos/LibraryOptionsResultDto.cs | 40 + .../LibraryDtos/LibraryTypeOptionsDto.cs | 37 + .../Models/LibraryDtos/MediaUpdateInfoDto.cs | 15 + .../LibraryDtos/MediaUpdateInfoPathDto.cs | 18 + .../AddVirtualFolderDto.cs | 14 + .../LibraryStructureDto/MediaPathDto.cs | 26 + .../UpdateLibraryOptionsDto.cs | 20 + .../UpdateMediaPathRequestDto.cs | 22 + .../Models/LiveTvDtos/GetProgramsDto.cs | 160 + .../Models/LiveTvDtos/SetChannelMappingDto.cs | 27 + .../Models/MediaInfoDtos/OpenLiveStreamDto.cs | 82 + .../Models/MediaInfoDtos/PlaybackInfoDto.cs | 90 + .../Models/PlaylistDtos/CreatePlaylistDto.cs | 45 + .../Models/PlaylistDtos/UpdatePlaylistDto.cs | 34 + .../PlaylistDtos/UpdatePlaylistUserDto.cs | 12 + .../StartupDtos/StartupConfigurationDto.cs | 27 + .../StartupDtos/StartupRemoteAccessDto.cs | 15 + .../Models/StartupDtos/StartupUserDto.cs | 17 + .../StreamingDtos/HlsAudioRequestDto.cs | 14 + .../StreamingDtos/HlsVideoRequestDto.cs | 14 + .../Models/SubtitleDtos/UploadSubtitleDto.cs | 39 + .../Models/SyncPlayDtos/BufferRequestDto.cs | 41 + .../SyncPlayDtos/IgnoreWaitRequestDto.cs | 13 + .../SyncPlayDtos/JoinGroupRequestDto.cs | 15 + .../MovePlaylistItemRequestDto.cs | 29 + .../Models/SyncPlayDtos/NewGroupRequestDto.cs | 21 + .../Models/SyncPlayDtos/NextItemRequestDto.cs | 23 + .../Models/SyncPlayDtos/PingRequestDto.cs | 13 + .../Models/SyncPlayDtos/PlayRequestDto.cs | 36 + .../SyncPlayDtos/PreviousItemRequestDto.cs | 23 + .../Models/SyncPlayDtos/QueueRequestDto.cs | 31 + .../Models/SyncPlayDtos/ReadyRequestDto.cs | 41 + .../RemoveFromPlaylistRequestDto.cs | 36 + .../Models/SyncPlayDtos/SeekRequestDto.cs | 13 + .../SyncPlayDtos/SetPlaylistItemRequestDto.cs | 23 + .../SyncPlayDtos/SetRepeatModeRequestDto.cs | 15 + .../SyncPlayDtos/SetShuffleModeRequestDto.cs | 15 + .../Models/SystemInfoDtos/FolderStorageDto.cs | 46 + .../SystemInfoDtos/LibraryStorageDto.cs | 37 + .../Models/SystemInfoDtos/SystemStorageDto.cs | 67 + .../Models/UserDtos/AuthenticateUserByName.cs | 17 + .../Models/UserDtos/CreateUserByName.cs | 20 + .../Models/UserDtos/ForgotPasswordDto.cs | 15 + .../Models/UserDtos/ForgotPasswordPinDto.cs | 15 + .../Models/UserDtos/QuickConnectDto.cs | 15 + .../Models/UserDtos/UpdateUserPassword.cs | 27 + .../UserViewDtos/SpecialViewOptionDto.cs | 17 + Jellyfin.Api/Results/OkResultOfT.cs | 21 + .../ActivityLogWebSocketListener.cs | 88 + .../ScheduledTasksWebSocketListener.cs | 89 + .../SessionInfoWebSocketListener.cs | 145 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + .../net10.0/Jellyfin.Api.AssemblyInfo.cs | 23 + .../Jellyfin.Api.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../Debug/net10.0/Jellyfin.Api.assets.cache | Bin 0 -> 28557 bytes ...ellyfin.Api.csproj.AssemblyReference.cache | Bin 0 -> 9571 bytes .../obj/Jellyfin.Api.csproj.nuget.dgspec.json | 6213 ++++++ .../obj/Jellyfin.Api.csproj.nuget.g.props | 29 + .../obj/Jellyfin.Api.csproj.nuget.g.targets | 10 + Jellyfin.Api/obj/project.assets.json | 2992 +++ Jellyfin.Api/obj/project.nuget.cache | 58 + Jellyfin.Api/obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + Jellyfin.Api/obj/rider.project.restore.info | 1 + .../Attributes/OpenApiIgnoreEnumAttribute.cs | 11 + Jellyfin.Data/DayOfWeekHelper.cs | 32 + Jellyfin.Data/Dtos/DeviceOptionsDto.cs | 22 + Jellyfin.Data/Enums/ActivityLogSortBy.cs | 49 + Jellyfin.Data/Enums/AudioSpatialFormat.cs | 22 + Jellyfin.Data/Enums/BaseItemKind.cs | 205 + Jellyfin.Data/Enums/CollectionType.cs | 165 + Jellyfin.Data/Enums/ItemSortBy.cs | 157 + Jellyfin.Data/Enums/MediaStreamProtocol.cs | 22 + Jellyfin.Data/Enums/MediaType.cs | 32 + Jellyfin.Data/Enums/PersonKind.cs | 133 + .../Enums/SyncPlayAccessRequirementType.cs | 28 + Jellyfin.Data/Enums/UnratedItem.cs | 53 + Jellyfin.Data/Enums/VideoRange.cs | 22 + Jellyfin.Data/Enums/VideoRangeType.cs | 73 + Jellyfin.Data/Events/GenericEventArgs.cs | 26 + .../Events/System/PendingRestartEventArgs.cs | 11 + .../Events/Users/UserCreatedEventArgs.cs | 18 + .../Events/Users/UserDeletedEventArgs.cs | 18 + .../Events/Users/UserLockedOutEventArgs.cs | 18 + .../Users/UserPasswordChangedEventArgs.cs | 18 + .../Events/Users/UserUpdatedEventArgs.cs | 18 + Jellyfin.Data/Jellyfin.Data.csproj | 53 + Jellyfin.Data/Queries/ActivityLogQuery.cs | 68 + Jellyfin.Data/Queries/DeviceQuery.cs | 25 + Jellyfin.Data/Queries/PaginatedQuery.cs | 18 + Jellyfin.Data/UserEntityExtensions.cs | 220 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../Debug/net10.0/Jellyfin.Data.assets.cache | Bin 0 -> 12114 bytes ...llyfin.Data.csproj.AssemblyReference.cache | Bin 0 -> 7025 bytes .../Jellyfin.Data.csproj.nuget.dgspec.json | 1161 ++ .../obj/Jellyfin.Data.csproj.nuget.g.props | 26 + .../obj/Jellyfin.Data.csproj.nuget.g.targets | 8 + Jellyfin.Data/obj/project.assets.json | 1406 ++ Jellyfin.Data/obj/project.nuget.cache | 30 + Jellyfin.Data/obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + Jellyfin.Data/obj/rider.project.restore.info | 1 + .../Activity/ActivityManager.cs | 213 + .../DatabaseConfigurationFactory.cs | 17 + .../DatabaseConfigurationStore.cs | 26 + .../Devices/DeviceManager.cs | 311 + .../Library/LyricDownloadFailureLogger.cs | 101 + .../Library/SubtitleDownloadFailureLogger.cs | 102 + .../Security/AuthenticationFailedLogger.cs | 51 + .../Security/AuthenticationSucceededLogger.cs | 48 + .../Consumers/Session/PlaybackStartLogger.cs | 108 + .../Consumers/Session/PlaybackStopLogger.cs | 116 + .../Consumers/Session/SessionEndedLogger.cs | 54 + .../Consumers/Session/SessionStartedLogger.cs | 54 + .../System/PendingRestartNotifier.cs | 31 + .../Consumers/System/TaskCompletedLogger.cs | 158 + .../Consumers/System/TaskCompletedNotifier.cs | 32 + .../PluginInstallationCancelledNotifier.cs | 32 + .../Updates/PluginInstallationFailedLogger.cs | 51 + .../PluginInstallationFailedNotifier.cs | 32 + .../Updates/PluginInstalledLogger.cs | 50 + .../Updates/PluginInstalledNotifier.cs | 32 + .../Updates/PluginInstallingNotifier.cs | 32 + .../Updates/PluginUninstalledLogger.cs | 45 + .../Updates/PluginUninstalledNotifier.cs | 32 + .../Consumers/Updates/PluginUpdatedLogger.cs | 51 + .../Consumers/Users/UserCreatedLogger.cs | 43 + .../Consumers/Users/UserDeletedLogger.cs | 44 + .../Consumers/Users/UserDeletedNotifier.cs | 39 + .../Consumers/Users/UserLockedOutLogger.cs | 47 + .../Users/UserPasswordChangedLogger.cs | 43 + .../Consumers/Users/UserUpdatedNotifier.cs | 42 + .../Events/EventManager.cs | 65 + .../EventingServiceCollectionExtensions.cs | 72 + .../Extensions/ExpressionExtensions.cs | 70 + .../Extensions/ServiceCollectionExtensions.cs | 149 + .../FullSystemBackup/BackupManifest.cs | 19 + .../FullSystemBackup/BackupOptions.cs | 15 + .../FullSystemBackup/BackupService.cs | 560 + .../Item/BaseItemRepository.cs | 2735 +++ .../Item/ChapterRepository.cs | 128 + .../Item/KeyframeRepository.cs | 75 + .../Item/MediaAttachmentRepository.cs | 82 + .../Item/MediaStreamRepository.cs | 233 + .../Item/OrderMapper.cs | 98 + .../Item/PeopleRepository.cs | 254 + .../Jellyfin.Server.Implementations.csproj | 41 + .../MediaSegments/MediaSegmentManager.cs | 289 + .../Properties/AssemblyInfo.cs | 23 + .../Security/AuthenticationManager.cs | 69 + .../Security/AuthorizationContext.cs | 319 + .../StorageHelpers/StorageHelper.cs | 108 + .../Trickplay/TrickplayManager.cs | 695 + .../Users/DefaultAuthenticationProvider.cs | 111 + .../Users/DefaultPasswordResetProvider.cs | 139 + .../Users/DeviceAccessHost.cs | 77 + .../Users/DisplayPreferencesManager.cs | 116 + .../Users/InvalidAuthProvider.cs | 30 + .../Users/UserManager.cs | 894 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + ...llyfin.Server.Implementations.assets.cache | Bin 0 -> 26521 bytes ...ementations.csproj.AssemblyReference.cache | Bin 0 -> 7784 bytes ...r.Implementations.csproj.nuget.dgspec.json | 5260 +++++ ...erver.Implementations.csproj.nuget.g.props | 26 + ...ver.Implementations.csproj.nuget.g.targets | 10 + .../obj/project.assets.json | 2342 +++ .../obj/project.nuget.cache | 47 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../Configuration/CorsPolicyProvider.cs | 49 + Jellyfin.Server/CoreAppHost.cs | 130 + .../ApiApplicationBuilderExtensions.cs | 121 + .../ApiServiceCollectionExtensions.cs | 362 + .../Extensions/WebHostBuilderExtensions.cs | 136 + .../Filters/AdditionalModelFilter.cs | 230 + .../Filters/CachingOpenApiProvider.cs | 93 + Jellyfin.Server/Filters/FileRequestFilter.cs | 43 + Jellyfin.Server/Filters/FileResponseFilter.cs | 53 + .../Filters/FlagsEnumSchemaFilter.cs | 53 + .../Filters/IgnoreEnumSchemaFilter.cs | 42 + .../Filters/ParameterObsoleteFilter.cs | 36 + .../RetryOnTemporarilyUnavailableFilter.cs | 51 + .../SecurityRequirementsOperationFilter.cs | 98 + .../DbContextFactoryHealthCheck.cs | 43 + Jellyfin.Server/Helpers/StartupHelpers.cs | 300 + Jellyfin.Server/Jellyfin.Server.csproj | 86 + Jellyfin.Server/Jellyfin.Server.ico | Bin 0 -> 40883 bytes .../Migrations/IAsyncMigrationRoutine.cs | 31 + .../Migrations/IDatabaseMigrationRoutine.cs | 14 + .../Migrations/JellyfinMigrationAttribute.cs | 68 + .../JellyfinMigrationBackupAttribute.cs | 35 + .../Migrations/JellyfinMigrationService.cs | 459 + .../Migrations/MigrationOptions.cs | 27 + .../CreateNetworkConfiguration.cs | 129 + .../MigrateEncodingOptions.cs | 239 + .../MigrateMusicBrainzTimeout.cs | 92 + .../MigrateNetworkConfiguration.cs | 205 + .../RenameEnableGroupingIntoCollections.cs | 57 + .../Routines/AddDefaultCastReceivers.cs | 45 + .../Routines/AddDefaultPluginRepository.cs | 39 + .../Migrations/Routines/CleanMusicArtist.cs | 47 + .../Routines/CreateUserLoggingConfigFile.cs | 76 + .../Routines/DisableLegacyAuthorization.cs | 32 + .../Routines/DisableTranscodingThrottling.cs | 38 + .../Migrations/Routines/FixAudioData.cs | 77 + .../Migrations/Routines/FixDates.cs | 171 + .../Migrations/Routines/FixPlaylistOwner.cs | 70 + .../Routines/MigrateActivityLogDb.cs | 163 + .../Routines/MigrateAuthenticationDb.cs | 162 + .../Routines/MigrateDisplayPreferencesDb.cs | 242 + .../Routines/MigrateKeyframeData.cs | 169 + .../Migrations/Routines/MigrateLibraryDb.cs | 1472 ++ .../MigrateLibraryDbCompatibilityCheck.cs | 73 + .../Routines/MigrateLibraryUserData.cs | 123 + .../Routines/MigrateRatingLevels.cs | 69 + .../Migrations/Routines/MigrateUserDb.cs | 233 + .../Migrations/Routines/MoveExtractedFiles.cs | 319 + .../Migrations/Routines/MoveTrickplayFiles.cs | 129 + .../Routines/ReaddDefaultPluginRepository.cs | 42 + .../Migrations/Routines/RefreshCleanNames.cs | 105 + .../Routines/RefreshInternalDateModified.cs | 131 + .../Routines/RemoveDownloadImagesInAdvance.cs | 45 + .../Routines/RemoveDuplicateExtras.cs | 76 + .../RemoveDuplicatePlaylistChildren.cs | 58 + .../Migrations/Routines/ReseedFolderFlag.cs | 74 + .../Routines/UpdateDefaultPluginRepository.cs | 46 + .../Migrations/Stages/CodeMigration.cs | 87 + .../Stages/JellyfinMigrationStageTypes.cs | 26 + .../Migrations/Stages/MigrationStage.cs | 16 + Jellyfin.Server/Program.cs | 357 + Jellyfin.Server/Properties/AssemblyInfo.cs | 24 + .../Properties/launchSettings.json | 29 + .../Resources/Configuration/logging.json | 38 + .../ServerSetupApp/IStartupLogger.cs | 66 + Jellyfin.Server/ServerSetupApp/SetupServer.cs | 376 + .../ServerSetupApp/StartupLogTopic.cs | 31 + .../ServerSetupApp/StartupLogger.cs | 124 + .../ServerSetupApp/StartupLoggerExtensions.cs | 18 + .../ServerSetupApp/StartupLoggerOfCategory.cs | 56 + .../ServerSetupApp/index.mstemplate.html | 235 + Jellyfin.Server/Startup.cs | 235 + Jellyfin.Server/StartupOptions.cs | 113 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 41 + .../net10.0/Jellyfin.Server.assets.cache | Bin 0 -> 56091 bytes ...yfin.Server.csproj.AssemblyReference.cache | Bin 0 -> 32681 bytes .../Jellyfin.Server.csproj.nuget.dgspec.json | 11815 +++++++++++ .../obj/Jellyfin.Server.csproj.nuget.g.props | 27 + .../Jellyfin.Server.csproj.nuget.g.targets | 7 + Jellyfin.Server/obj/project.assets.json | 4949 +++++ Jellyfin.Server/obj/project.nuget.cache | 102 + Jellyfin.Server/obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + Jellyfin.Server/wwwroot/api-docs/jellyfin.svg | 26 + .../wwwroot/api-docs/redoc/custom.css | 0 .../wwwroot/api-docs/swagger/custom.css | 17 + Jellyfin.sln | 322 + Jellyfin.sln.DotSettings | 4 + LICENSE | 339 + MediaBrowser.Common/Api/Policies.cs | 97 + .../Configuration/ConfigurationStore.cs | 22 + .../ConfigurationUpdateEventArgs.cs | 33 + .../EncodingConfigurationExtensions.cs | 42 + .../Configuration/IApplicationPaths.cs | 113 + .../Configuration/IConfigurationFactory.cs | 17 + .../Configuration/IConfigurationManager.cs | 97 + .../Configuration/IValidatingConfiguration.cs | 15 + MediaBrowser.Common/Crc32.cs | 89 + MediaBrowser.Common/Events/EventHelper.cs | 64 + .../Extensions/BaseExtensions.cs | 37 + .../Extensions/HttpContextExtensions.cs | 41 + .../Extensions/MethodNotAllowedException.cs | 26 + .../Extensions/ProcessExtensions.cs | 28 + .../Extensions/RateLimitExceededException.cs | 25 + .../Extensions/ResourceNotFoundException.cs | 26 + MediaBrowser.Common/FfmpegException.cs | 39 + MediaBrowser.Common/IApplicationHost.cs | 125 + .../MediaBrowser.Common.csproj | 61 + MediaBrowser.Common/Net/INetworkManager.cs | 133 + MediaBrowser.Common/Net/NamedClient.cs | 28 + .../Net/NetworkConfiguration.cs | 176 + .../Net/NetworkConfigurationExtensions.cs | 19 + .../Net/NetworkConfigurationFactory.cs | 22 + .../Net/NetworkConfigurationStore.cs | 23 + MediaBrowser.Common/Net/NetworkConstants.cs | 79 + MediaBrowser.Common/Net/NetworkUtils.cs | 360 + .../Net/RemoteAccessPolicyResult.cs | 29 + MediaBrowser.Common/Plugins/BasePlugin.cs | 94 + MediaBrowser.Common/Plugins/BasePluginOfT.cs | 200 + .../Plugins/IHasPluginConfiguration.cs | 27 + MediaBrowser.Common/Plugins/IPlugin.cs | 59 + .../Plugins/IPluginAssembly.cs | 13 + MediaBrowser.Common/Plugins/IPluginManager.cs | 96 + MediaBrowser.Common/Plugins/LocalPlugin.cs | 139 + MediaBrowser.Common/Plugins/PluginManifest.cs | 117 + .../Properties/AssemblyInfo.cs | 21 + .../Providers/ProviderIdParsers.cs | 123 + .../Providers/SubtitleConfigurationFactory.cs | 21 + .../RequiresSourceSerialisationAttribute.cs | 11 + .../Updates/IInstallationManager.cs | 95 + .../Updates/InstallationEventArgs.cs | 23 + .../Updates/InstallationFailedEventArgs.cs | 12 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../net10.0/MediaBrowser.Common.assets.cache | Bin 0 -> 7601 bytes ...wser.Common.csproj.AssemblyReference.cache | Bin 0 -> 3805 bytes ...diaBrowser.Common.csproj.nuget.dgspec.json | 2862 +++ .../MediaBrowser.Common.csproj.nuget.g.props | 26 + ...MediaBrowser.Common.csproj.nuget.g.targets | 6 + MediaBrowser.Common/obj/project.assets.json | 1253 ++ MediaBrowser.Common/obj/project.nuget.cache | 25 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../Authentication/AuthenticationException.cs | 35 + .../Authentication/AuthenticationResult.cs | 31 + .../Authentication/IAuthenticationProvider.cs | 36 + .../Authentication/IPasswordResetProvider.cs | 28 + .../BaseItemManager/BaseItemManager.cs | 73 + .../BaseItemManager/IBaseItemManager.cs | 29 + MediaBrowser.Controller/Channels/Channel.cs | 87 + .../Channels/ChannelItemInfo.cs | 97 + .../Channels/ChannelItemResult.cs | 19 + .../Channels/ChannelItemType.cs | 11 + .../Channels/ChannelLatestMediaSearch.cs | 11 + .../Channels/ChannelParentalRating.cs | 17 + .../Channels/ChannelSearchInfo.cs | 13 + MediaBrowser.Controller/Channels/IChannel.cs | 80 + .../Channels/IChannelManager.cs | 99 + .../Channels/IDisableMediaSourceDisplay.cs | 12 + .../Channels/IHasCacheKey.cs | 14 + .../Channels/IHasFolderAttributes.cs | 9 + .../Channels/IRequiresMediaInfoCallback.cs | 21 + .../Channels/ISupportsDelete.cs | 15 + .../Channels/ISupportsLatestMedia.cs | 19 + .../Channels/ISupportsMediaProbe.cs | 9 + .../Channels/InternalChannelFeatures.cs | 66 + .../Channels/InternalChannelItemQuery.cs | 24 + .../Chapters/IChapterManager.cs | 57 + .../ClientEvent/ClientEventLogger.cs | 34 + .../ClientEvent/IClientEventLogger.cs | 23 + .../Collections/CollectionCreatedEventArgs.cs | 24 + .../Collections/CollectionCreationOptions.cs | 32 + .../CollectionModifiedEventArgs.cs | 30 + .../Collections/ICollectionManager.cs | 67 + .../IServerConfigurationManager.cs | 23 + .../Devices/IDeviceManager.cs | 117 + .../Drawing/IImageEncoder.cs | 95 + .../Drawing/IImageProcessor.cs | 117 + .../Drawing/ImageCollageOptions.cs | 35 + .../Drawing/ImageHelper.cs | 17 + .../Drawing/ImageProcessingOptions.cs | 126 + .../Drawing/ImageProcessorExtensions.cs | 27 + MediaBrowser.Controller/Dto/DtoOptions.cs | 68 + MediaBrowser.Controller/Dto/IDtoService.cs | 52 + .../Entities/AggregateFolder.cs | 204 + .../Entities/Audio/Audio.cs | 153 + .../Entities/Audio/IHasAlbumArtist.cs | 33 + .../Entities/Audio/IHasMusicGenres.cs | 11 + .../Entities/Audio/MusicAlbum.cs | 227 + .../Entities/Audio/MusicArtist.cs | 231 + .../Entities/Audio/MusicGenre.cs | 127 + MediaBrowser.Controller/Entities/AudioBook.cs | 65 + MediaBrowser.Controller/Entities/BaseItem.cs | 2700 +++ .../Entities/BaseItemExtensions.cs | 132 + .../Entities/BasePluginFolder.cs | 33 + MediaBrowser.Controller/Entities/Book.cs | 88 + .../Entities/CollectionFolder.cs | 373 + .../Entities/Extensions.cs | 42 + MediaBrowser.Controller/Entities/Folder.cs | 1881 ++ MediaBrowser.Controller/Entities/Genre.cs | 130 + .../Entities/ICollectionFolder.cs | 33 + .../Entities/IHasAspectRatio.cs | 16 + .../Entities/IHasDisplayOrder.cs | 18 + .../Entities/IHasMediaSources.cs | 29 + .../Entities/IHasProgramAttributes.cs | 29 + .../Entities/IHasSeries.cs | 29 + .../Entities/IHasSpecialFeatures.cs | 18 + .../Entities/IHasStartDate.cs | 11 + .../Entities/IHasTrailers.cs | 38 + .../Entities/IItemByName.cs | 19 + .../Entities/IMetadataContainer.cs | 21 + .../Entities/ISupportsBoxSetGrouping.cs | 11 + .../Entities/ISupportsPlaceHolders.cs | 13 + .../Entities/InternalItemsQuery.cs | 392 + .../Entities/InternalPeopleQuery.cs | 45 + .../Entities/ItemImageInfo.cs | 42 + .../Entities/LinkedChild.cs | 45 + .../Entities/LinkedChildComparer.cs | 35 + .../Entities/LinkedChildType.cs | 18 + .../Entities/MediaSourceWidthComparator.cs | 56 + .../Entities/Movies/BoxSet.cs | 235 + .../Entities/Movies/Movie.cs | 125 + .../Entities/MusicVideo.cs | 75 + .../Entities/PeopleHelper.cs | 100 + MediaBrowser.Controller/Entities/Person.cs | 149 + .../Entities/PersonInfo.cs | 69 + MediaBrowser.Controller/Entities/Photo.cs | 98 + .../Entities/PhotoAlbum.cs | 19 + .../Entities/SourceType.cs | 11 + MediaBrowser.Controller/Entities/Studio.cs | 125 + .../Entities/TV/Episode.cs | 382 + MediaBrowser.Controller/Entities/TV/Season.cs | 288 + MediaBrowser.Controller/Entities/TV/Series.cs | 528 + .../Entities/TagExtensions.cs | 33 + MediaBrowser.Controller/Entities/Trailer.cs | 84 + .../Entities/UserItemData.cs | 121 + .../Entities/UserRootFolder.cs | 141 + MediaBrowser.Controller/Entities/UserView.cs | 192 + .../Entities/UserViewBuilder.cs | 1027 + MediaBrowser.Controller/Entities/Video.cs | 566 + MediaBrowser.Controller/Entities/Year.cs | 132 + .../AuthenticationRequestEventArgs.cs | 60 + .../AuthenticationResultEventArgs.cs | 37 + .../Events/IEventConsumer.cs | 20 + .../Events/IEventManager.cs | 28 + .../Events/Session/SessionEndedEventArgs.cs | 19 + .../Events/Session/SessionStartedEventArgs.cs | 19 + .../PluginInstallationCancelledEventArgs.cs | 19 + .../Updates/PluginInstalledEventArgs.cs | 19 + .../Updates/PluginInstallingEventArgs.cs | 19 + .../Updates/PluginUninstalledEventArgs.cs | 19 + .../Events/Updates/PluginUpdatedEventArgs.cs | 19 + .../Extensions/ConfigurationExtensions.cs | 150 + .../Extensions/XmlReaderExtensions.cs | 193 + .../IDisplayPreferencesManager.cs | 74 + MediaBrowser.Controller/IO/FileData.cs | 108 + .../IO/FileSystemHelper.cs | 169 + .../IO/IExternalDataManager.cs | 19 + MediaBrowser.Controller/IO/IPathManager.cs | 71 + .../IServerApplicationHost.cs | 95 + .../IServerApplicationPaths.cs | 86 + MediaBrowser.Controller/ISystemManager.cs | 41 + .../Library/DeleteOptions.cs | 16 + .../Library/IDirectStreamProvider.cs | 19 + .../Library/IIntroProvider.cs | 29 + .../Library/IKeyframeManager.cs | 37 + .../Library/ILibraryManager.cs | 671 + .../Library/ILibraryMonitor.cs | 37 + .../Library/ILibraryPostScanTask.cs | 20 + .../Library/ILiveStream.cs | 33 + .../Library/IMediaSourceManager.cs | 145 + .../Library/IMediaSourceProvider.cs | 30 + .../Library/IMetadataFileSaver.cs | 16 + .../Library/IMetadataSaver.cs | 34 + .../Library/IMusicManager.cs | 40 + .../Library/ISearchEngine.cs | 18 + .../Library/IUserDataManager.cs | 76 + .../Library/IUserManager.cs | 171 + .../Library/IUserViewManager.cs | 42 + MediaBrowser.Controller/Library/IntroInfo.cs | 23 + .../Library/ItemChangeEventArgs.cs | 28 + .../Library/ItemResolveArgs.cs | 286 + .../Library/ItemUpdateType.cs | 16 + .../Library/LibraryManagerExtensions.cs | 15 + .../MetadataConfigurationExtensions.cs | 24 + .../Library/MetadataConfigurationStore.cs | 23 + .../Library/NameExtensions.cs | 25 + .../Library/PlaybackProgressEventArgs.cs | 48 + .../Library/PlaybackStartEventArgs.cs | 9 + .../Library/PlaybackStopEventArgs.cs | 13 + .../Library/SearchHintInfo.cs | 24 + MediaBrowser.Controller/Library/TVUtils.cs | 49 + .../Library/UserDataSaveEventArgs.cs | 43 + .../ILimitedConcurrencyLibraryScheduler.cs | 23 + .../LimitedConcurrencyLibraryScheduler.cs | 314 + .../LiveTv/ActiveRecordingInfo.cs | 19 + MediaBrowser.Controller/LiveTv/ChannelInfo.cs | 88 + .../LiveTv/IGuideManager.cs | 26 + .../LiveTv/IListingsManager.cs | 79 + .../LiveTv/IListingsProvider.cs | 28 + .../LiveTv/ILiveTvManager.cs | 233 + .../LiveTv/ILiveTvService.cs | 175 + .../LiveTv/IRecordingsManager.cs | 55 + MediaBrowser.Controller/LiveTv/ITunerHost.cs | 68 + .../LiveTv/ITunerHostManager.cs | 46 + .../LiveTv/LiveTvChannel.cs | 157 + .../LiveTv/LiveTvConflictException.cs | 17 + .../LiveTv/LiveTvProgram.cs | 259 + MediaBrowser.Controller/LiveTv/ProgramInfo.cs | 227 + .../LiveTv/SeriesTimerInfo.cs | 127 + .../LiveTv/TimerEventInfo.cs | 18 + MediaBrowser.Controller/LiveTv/TimerInfo.cs | 166 + .../Lyrics/ILyricManager.cs | 119 + .../Lyrics/ILyricParser.cs | 28 + .../Lyrics/ILyricProvider.cs | 34 + .../Lyrics/LyricDownloadFailureEventArgs.cs | 26 + .../MediaBrowser.Controller.csproj | 66 + .../MediaEncoding/BaseEncodingJobOptions.cs | 218 + .../BitStreamFilterOptionType.cs | 32 + .../MediaEncoding/DownMixAlgorithmsHelper.cs | 74 + .../MediaEncoding/EncodingHelper.cs | 7818 +++++++ .../MediaEncoding/EncodingJobInfo.cs | 738 + .../MediaEncoding/FilterOptionType.cs | 53 + .../MediaEncoding/IAttachmentExtractor.cs | 41 + .../MediaEncoding/IMediaEncoder.cs | 285 + .../MediaEncoding/ISubtitleEncoder.cs | 65 + .../MediaEncoding/ITranscodeManager.cs | 105 + .../MediaEncoding/JobLogger.cs | 163 + .../MediaEncoding/MediaInfoRequest.cs | 18 + .../MediaEncoding/TranscodingJob.cs | 288 + .../MediaEncoding/TranscodingJobType.cs | 23 + .../TranscodingSegmentCleaner.cs | 178 + .../MediaEncoding/TranscodingThrottler.cs | 218 + .../MediaSegments/IMediaSegmentManager.cs | 82 + .../MediaSegments/IMediaSegmentProvider.cs | 34 + .../Net/AuthorizationInfo.cs | 69 + .../Net/BasePeriodicWebSocketListener.cs | 316 + MediaBrowser.Controller/Net/IAuthService.cs | 18 + .../Net/IAuthorizationContext.cs | 25 + .../Net/IWebSocketConnection.cs | 81 + .../Net/IWebSocketListener.cs | 26 + .../Net/IWebSocketManager.cs | 18 + .../Net/SecurityException.cs | 37 + .../Net/WebSocketListenerState.cs | 17 + .../Net/WebSocketMessage.cs | 22 + .../Net/WebSocketMessageInfo.cs | 18 + .../Net/WebSocketMessageOfT.cs | 32 + .../IInboundWebSocketMessage.cs | 10 + .../IOutboundWebSocketMessage.cs | 10 + .../Inbound/ActivityLogEntryStartMessage.cs | 25 + .../Inbound/ActivityLogEntryStopMessage.cs | 14 + .../Inbound/InboundKeepAliveMessage.cs | 14 + .../Inbound/ScheduledTasksInfoStartMessage.cs | 24 + .../Inbound/ScheduledTasksInfoStopMessage.cs | 14 + .../Inbound/SessionsStartMessage.cs | 24 + .../Inbound/SessionsStopMessage.cs | 14 + .../InboundWebSocketMessage.cs | 8 + .../InboundWebSocketMessageOfT.cs | 26 + .../Outbound/ActivityLogEntryMessage.cs | 25 + .../Outbound/ForceKeepAliveMessage.cs | 23 + .../Outbound/GeneralCommandMessage.cs | 23 + .../Outbound/LibraryChangedMessage.cs | 24 + .../Outbound/OutboundKeepAliveMessage.cs | 14 + .../WebSocketMessages/Outbound/PlayMessage.cs | 23 + .../Outbound/PlaystateMessage.cs | 23 + .../PluginInstallationCancelledMessage.cs | 24 + .../PluginInstallationCompletedMessage.cs | 24 + .../PluginInstallationFailedMessage.cs | 24 + .../Outbound/PluginInstallingMessage.cs | 24 + .../Outbound/PluginUninstalledMessage.cs | 24 + .../Outbound/RefreshProgressMessage.cs | 24 + .../Outbound/RestartRequiredMessage.cs | 14 + .../Outbound/ScheduledTaskEndedMessage.cs | 24 + .../Outbound/ScheduledTasksInfoMessage.cs | 25 + .../Outbound/SeriesTimerCancelledMessage.cs | 24 + .../Outbound/SeriesTimerCreatedMessage.cs | 24 + .../Outbound/ServerRestartingMessage.cs | 14 + .../Outbound/ServerShuttingDownMessage.cs | 14 + .../Outbound/SessionsMessage.cs | 26 + .../Outbound/SyncPlayCommandMessage.cs | 24 + .../Outbound/TimerCancelledMessage.cs | 24 + .../Outbound/TimerCreatedMessage.cs | 24 + .../Outbound/UserDataChangedMessage.cs | 23 + .../Outbound/UserDeletedMessage.cs | 24 + .../Outbound/UserUpdatedMessage.cs | 24 + .../OutboundWebSocketMessage.cs | 14 + .../OutboundWebSocketMessageOfT.cs | 33 + .../Persistence/IChapterRepository.cs | 43 + .../Persistence/IItemRepository.cs | 141 + .../Persistence/IItemTypeLookup.cs | 22 + .../Persistence/IKeyframeRepository.cs | 37 + .../Persistence/IMediaAttachmentRepository.cs | 28 + .../Persistence/IMediaStreamRepository.cs | 31 + .../Persistence/IPeopleRepository.cs | 33 + .../Persistence/MediaAttachmentQuery.cs | 21 + .../Persistence/MediaStreamQuery.cs | 28 + .../Playlists/IPlaylistManager.cs | 114 + MediaBrowser.Controller/Playlists/Playlist.cs | 276 + .../Plugins/IPluginServiceRegistrator.cs | 19 + .../Properties/AssemblyInfo.cs | 24 + .../Providers/AlbumInfo.cs | 31 + .../Providers/ArtistInfo.cs | 16 + MediaBrowser.Controller/Providers/BookInfo.cs | 11 + .../Providers/BoxSetInfo.cs | 8 + .../Providers/DirectoryService.cs | 116 + .../Providers/DynamicImageResponse.cs | 44 + .../Providers/EpisodeInfo.cs | 29 + .../Providers/ICustomMetadataProvider.cs | 26 + .../Providers/IDirectoryService.cs | 28 + .../Providers/IDynamicImageProvider.cs | 29 + .../Providers/IExternalId.cs | 41 + .../Providers/IExternalUrlProvider.cs | 22 + .../Providers/IForcedProvider.cs | 9 + .../Providers/IHasItemChangeMonitor.cs | 17 + .../Providers/IHasLookupInfo.cs | 10 + .../Providers/IHasOrder.cs | 14 + .../Providers/IImageProvider.cs | 23 + .../Providers/ILocalImageProvider.cs | 15 + .../Providers/ILocalMetadataProvider.cs | 28 + .../Providers/IMetadataProvider.cs | 23 + .../Providers/IMetadataService.cs | 42 + .../Providers/IPreRefreshProvider.cs | 8 + .../Providers/IProviderManager.cs | 222 + .../Providers/IRemoteImageProvider.cs | 39 + .../Providers/IRemoteMetadataProvider.cs | 49 + .../Providers/IRemoteSearchProvider.cs | 19 + .../Providers/ImageRefreshOptions.cs | 42 + MediaBrowser.Controller/Providers/ItemInfo.cs | 44 + .../Providers/ItemLookupInfo.cs | 69 + .../Providers/LocalImageInfo.cs | 16 + .../Providers/MetadataRefreshMode.cs | 27 + .../Providers/MetadataRefreshOptions.cs | 77 + .../Providers/MetadataResult.cs | 74 + .../Providers/MovieInfo.cs | 8 + .../Providers/MusicVideoInfo.cs | 13 + .../Providers/PersonLookupInfo.cs | 8 + .../Providers/RefreshPriority.cs | 23 + .../Providers/RemoteSearchQuery.cs | 27 + .../Providers/SeasonInfo.cs | 20 + .../Providers/SeriesInfo.cs | 8 + MediaBrowser.Controller/Providers/SongInfo.cs | 24 + .../Providers/TrailerInfo.cs | 8 + .../Providers/VideoContentType.cs | 18 + .../QuickConnect/IQuickConnect.cs | 48 + .../Resolvers/IItemResolver.cs | 52 + .../Resolvers/IResolverIgnoreRule.cs | 19 + .../Resolvers/ItemResolver.cs | 55 + .../Resolvers/ResolverPriority.cs | 43 + .../Security/AuthenticationInfo.cs | 75 + .../Security/IAuthenticationManager.cs | 31 + .../Session/AuthenticationRequest.cs | 30 + .../Session/ISessionController.cs | 35 + .../Session/ISessionManager.cs | 361 + .../Session/SessionEventArgs.cs | 13 + .../Session/SessionInfo.cs | 486 + .../Sorting/IBaseItemComparer.cs | 17 + .../Sorting/IUserBaseItemComparer.cs | 31 + .../Sorting/SortExtensions.cs | 36 + .../Streaming/ProgressiveFileStream.cs | 182 + .../Streaming/StreamState.cs | 183 + .../Streaming/StreamingRequestDto.cs | 49 + .../Streaming/VideoRequestDto.cs | 23 + .../Subtitles/ISubtitleManager.cs | 95 + .../Subtitles/ISubtitleProvider.cs | 43 + .../SubtitleDownloadFailureEventArgs.cs | 28 + .../Subtitles/SubtitleResponse.cs | 21 + .../Subtitles/SubtitleSearchRequest.cs | 57 + .../SyncPlay/GroupMember.cs | 60 + .../GroupStates/AbstractGroupState.cs | 233 + .../SyncPlay/GroupStates/IdleGroupState.cs | 128 + .../SyncPlay/GroupStates/PausedGroupState.cs | 161 + .../SyncPlay/GroupStates/PlayingGroupState.cs | 164 + .../SyncPlay/GroupStates/WaitingGroupState.cs | 680 + .../SyncPlay/IGroupPlaybackRequest.cs | 29 + .../SyncPlay/IGroupState.cs | 219 + .../SyncPlay/IGroupStateContext.cs | 221 + .../SyncPlay/ISyncPlayManager.cs | 73 + .../SyncPlay/ISyncPlayRequest.cs | 16 + .../AbstractPlaybackRequest.cs | 31 + .../PlaybackRequests/BufferGroupRequest.cs | 63 + .../IgnoreWaitGroupRequest.cs | 38 + .../MovePlaylistItemGroupRequest.cs | 47 + .../PlaybackRequests/NextItemGroupRequest.cs | 39 + .../PlaybackRequests/PauseGroupRequest.cs | 23 + .../PlaybackRequests/PingGroupRequest.cs | 38 + .../PlaybackRequests/PlayGroupRequest.cs | 56 + .../PreviousItemGroupRequest.cs | 39 + .../PlaybackRequests/QueueGroupRequest.cs | 48 + .../PlaybackRequests/ReadyGroupRequest.cs | 63 + .../RemoveFromPlaylistGroupRequest.cs | 56 + .../PlaybackRequests/SeekGroupRequest.cs | 38 + .../SetPlaylistItemGroupRequest.cs | 39 + .../SetRepeatModeGroupRequest.cs | 38 + .../SetShuffleModeGroupRequest.cs | 38 + .../PlaybackRequests/StopGroupRequest.cs | 21 + .../PlaybackRequests/UnpauseGroupRequest.cs | 21 + .../SyncPlay/Queue/PlayQueueManager.cs | 550 + .../SyncPlay/Requests/JoinGroupRequest.cs | 29 + .../SyncPlay/Requests/LeaveGroupRequest.cs | 13 + .../SyncPlay/Requests/ListGroupsRequest.cs | 13 + .../SyncPlay/Requests/NewGroupRequest.cs | 28 + .../SystemBackupService/BackupManifestDto.cs | 34 + .../SystemBackupService/BackupOptionsDto.cs | 29 + .../BackupRestoreRequestDto.cs | 15 + .../SystemBackupService/IBackupService.cs | 48 + .../TV/ITVSeriesManager.cs | 31 + .../Trickplay/ITrickplayManager.cs | 114 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../MediaBrowser.Controller.assets.cache | Bin 0 -> 16675 bytes ....Controller.csproj.AssemblyReference.cache | Bin 0 -> 4462 bytes ...rowser.Controller.csproj.nuget.dgspec.json | 4281 ++++ ...diaBrowser.Controller.csproj.nuget.g.props | 26 + ...aBrowser.Controller.csproj.nuget.g.targets | 9 + .../obj/project.assets.json | 1871 ++ .../obj/project.nuget.cache | 38 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + MediaBrowser.LocalMetadata/BaseXmlProvider.cs | 109 + .../CollectionFolderLocalImageProvider.cs | 45 + .../Images/EpisodeLocalImageProvider.cs | 85 + .../InternalMetadataFolderImageProvider.cs | 88 + .../Images/LocalImageProvider.cs | 495 + .../MediaBrowser.LocalMetadata.csproj | 38 + .../Parsers/BaseItemXmlParser.cs | 872 + .../Parsers/BoxSetXmlParser.cs | 104 + .../Parsers/PlaylistXmlParser.cs | 113 + .../Properties/AssemblyInfo.cs | 21 + .../Providers/BoxSetXmlProvider.cs | 44 + .../Providers/PlaylistXmlProvider.cs | 47 + .../Savers/BaseXmlSaver.cs | 512 + .../Savers/BoxSetXmlSaver.cs | 51 + .../Savers/PlaylistXmlSaver.cs | 83 + .../XmlProviderUtils.cs | 13 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../MediaBrowser.LocalMetadata.assets.cache | Bin 0 -> 16672 bytes ...calMetadata.csproj.AssemblyReference.cache | Bin 0 -> 4462 bytes ...ser.LocalMetadata.csproj.nuget.dgspec.json | 4749 +++++ ...Browser.LocalMetadata.csproj.nuget.g.props | 26 + ...owser.LocalMetadata.csproj.nuget.g.targets | 9 + .../obj/project.assets.json | 1875 ++ .../obj/project.nuget.cache | 38 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../Attachments/AttachmentExtractor.cs | 350 + .../BdInfo/BdInfoDirectoryInfo.cs | 129 + .../BdInfo/BdInfoExaminer.cs | 183 + .../BdInfo/BdInfoFileInfo.cs | 68 + .../EncodingConfigurationFactory.cs | 18 + .../EncodingConfigurationStore.cs | 50 + .../Encoder/ApplePlatformHelper.cs | 87 + .../Encoder/EncoderValidator.cs | 699 + .../Encoder/EncodingUtils.cs | 84 + .../Encoder/MediaEncoder.cs | 1399 ++ .../MediaBrowser.MediaEncoding.csproj | 47 + .../Probing/CodecType.cs | 32 + .../Probing/FFProbeHelpers.cs | 82 + .../Probing/InternalMediaInfoResult.cs | 41 + .../Probing/MediaChapter.cs | 35 + .../Probing/MediaFormatInfo.cs | 83 + .../Probing/MediaFrameInfo.cs | 184 + .../Probing/MediaFrameSideDataInfo.cs | 16 + .../Probing/MediaStreamInfo.cs | 321 + .../Probing/MediaStreamInfoSideData.cs | 80 + .../Probing/ProbeResultNormalizer.cs | 1732 ++ .../Properties/AssemblyInfo.cs | 23 + .../Subtitles/AssWriter.cs | 57 + .../Subtitles/ISubtitleParser.cs | 25 + .../Subtitles/ISubtitleWriter.cs | 20 + .../Subtitles/JsonWriter.cs | 44 + .../Subtitles/SrtWriter.cs | 49 + .../Subtitles/SsaWriter.cs | 57 + .../Subtitles/SubtitleEditParser.cs | 138 + .../Subtitles/SubtitleEncoder.cs | 1013 + .../Subtitles/SubtitleFormatExtensions.cs | 29 + .../Subtitles/TtmlWriter.cs | 60 + .../Subtitles/VttWriter.cs | 53 + .../Transcoding/TranscodeManager.cs | 757 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../MediaBrowser.MediaEncoding.assets.cache | Bin 0 -> 23554 bytes ...diaEncoding.csproj.AssemblyReference.cache | Bin 0 -> 7181 bytes ...ser.MediaEncoding.csproj.nuget.dgspec.json | 4777 +++++ ...Browser.MediaEncoding.csproj.nuget.g.props | 36 + ...owser.MediaEncoding.csproj.nuget.g.targets | 9 + .../obj/project.assets.json | 2388 +++ .../obj/project.nuget.cache | 49 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../Activity/ActivityLogEntry.cs | 85 + .../Activity/IActivityManager.cs | 40 + .../ApiClient/ServerDiscoveryInfo.cs | 43 + .../Branding/BrandingOptions.cs | 29 + .../Branding/BrandingOptionsDto.cs | 25 + .../Channels/ChannelFeatures.cs | 89 + .../Channels/ChannelFolderType.cs | 19 + .../Channels/ChannelItemSortField.cs | 15 + .../Channels/ChannelMediaContentType.cs | 23 + .../Channels/ChannelMediaType.cs | 13 + MediaBrowser.Model/Channels/ChannelQuery.cs | 59 + .../Collections/CollectionCreationResult.cs | 11 + .../BaseApplicationConfiguration.cs | 63 + .../Configuration/EmbeddedSubtitleOptions.cs | 28 + .../Configuration/EncodingOptions.cs | 312 + .../Configuration/HlsAudioSeekStrategy.cs | 23 + .../Configuration/ImageOption.cs | 32 + .../Configuration/ImageSavingConvention.cs | 10 + .../Configuration/LibraryOptions.cs | 153 + .../Configuration/MediaPathInfo.cs | 20 + .../Configuration/MetadataConfiguration.cs | 14 + .../Configuration/MetadataOptions.cs | 37 + .../Configuration/MetadataPlugin.cs | 20 + .../Configuration/MetadataPluginSummary.cs | 35 + .../Configuration/MetadataPluginType.cs | 20 + .../Configuration/PathSubstitution.cs | 18 + .../Configuration/ServerConfiguration.cs | 291 + .../Configuration/TrickplayOptions.cs | 71 + .../Configuration/TrickplayScanBehavior.cs | 17 + .../Configuration/TypeOptions.cs | 365 + .../Configuration/UserConfiguration.cs | 78 + .../Configuration/XbmcMetadataOptions.cs | 25 + MediaBrowser.Model/Cryptography/Constants.cs | 23 + .../Cryptography/ICryptoProvider.cs | 24 + .../Cryptography/PasswordHash.cs | 212 + MediaBrowser.Model/Devices/DeviceInfo.cs | 84 + MediaBrowser.Model/Dlna/CodecProfile.cs | 94 + MediaBrowser.Model/Dlna/CodecType.cs | 11 + MediaBrowser.Model/Dlna/ConditionProcessor.cs | 385 + MediaBrowser.Model/Dlna/ContainerProfile.cs | 49 + MediaBrowser.Model/Dlna/DeviceProfile.cs | 71 + MediaBrowser.Model/Dlna/DirectPlayProfile.cs | 65 + MediaBrowser.Model/Dlna/DlnaProfileType.cs | 13 + MediaBrowser.Model/Dlna/EncodingContext.cs | 10 + MediaBrowser.Model/Dlna/ITranscoderSupport.cs | 13 + MediaBrowser.Model/Dlna/MediaOptions.cs | 145 + MediaBrowser.Model/Dlna/PlaybackErrorCode.cs | 11 + MediaBrowser.Model/Dlna/ProfileCondition.cs | 40 + .../Dlna/ProfileConditionType.cs | 13 + .../Dlna/ProfileConditionValue.cs | 33 + .../Dlna/ResolutionConfiguration.cs | 17 + .../Dlna/ResolutionNormalizer.cs | 85 + MediaBrowser.Model/Dlna/ResolutionOptions.cs | 11 + MediaBrowser.Model/Dlna/StreamBuilder.cs | 2399 +++ MediaBrowser.Model/Dlna/StreamInfo.cs | 1389 ++ .../Dlna/SubtitleDeliveryMethod.cs | 35 + MediaBrowser.Model/Dlna/SubtitleProfile.cs | 62 + MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs | 26 + MediaBrowser.Model/Dlna/TranscodeSeekInfo.cs | 10 + MediaBrowser.Model/Dlna/TranscodingProfile.cs | 159 + MediaBrowser.Model/Drawing/DrawingUtils.cs | 126 + MediaBrowser.Model/Drawing/ImageDimensions.cs | 45 + MediaBrowser.Model/Drawing/ImageFormat.cs | 38 + .../Drawing/ImageFormatExtensions.cs | 46 + .../Drawing/ImageOrientation.cs | 16 + MediaBrowser.Model/Drawing/ImageResolution.cs | 52 + MediaBrowser.Model/Dto/BaseItemDto.cs | 798 + MediaBrowser.Model/Dto/BaseItemPerson.cs | 60 + .../Dto/ClientCapabilitiesDto.cs | 69 + MediaBrowser.Model/Dto/DeviceInfoDto.cs | 83 + .../Dto/DisplayPreferencesDto.cs | 106 + MediaBrowser.Model/Dto/IHasServerId.cs | 10 + MediaBrowser.Model/Dto/IItemDto.cs | 14 + MediaBrowser.Model/Dto/ImageInfo.cs | 58 + MediaBrowser.Model/Dto/ItemCounts.cs | 89 + MediaBrowser.Model/Dto/MediaSourceInfo.cs | 263 + MediaBrowser.Model/Dto/MediaSourceType.cs | 11 + MediaBrowser.Model/Dto/MetadataEditorInfo.cs | 55 + MediaBrowser.Model/Dto/NameGuidPair.cs | 14 + MediaBrowser.Model/Dto/NameIdPair.cs | 20 + MediaBrowser.Model/Dto/NameValuePair.cs | 30 + MediaBrowser.Model/Dto/PlaylistDto.cs | 26 + MediaBrowser.Model/Dto/RatingType.cs | 10 + MediaBrowser.Model/Dto/RecommendationDto.cs | 19 + MediaBrowser.Model/Dto/RecommendationType.cs | 19 + MediaBrowser.Model/Dto/SessionInfoDto.cs | 186 + MediaBrowser.Model/Dto/TrickplayInfoDto.cs | 62 + .../Dto/UpdateUserItemDataDto.cs | 76 + MediaBrowser.Model/Dto/UserDto.cs | 116 + MediaBrowser.Model/Dto/UserItemDataDto.cs | 76 + MediaBrowser.Model/Entities/ChapterInfo.cs | 34 + .../Entities/CollectionTypeOptions.cs | 49 + .../Entities/DeinterlaceMethod.cs | 19 + .../Entities/DownMixStereoAlgorithms.cs | 35 + MediaBrowser.Model/Entities/EncoderPreset.cs | 64 + MediaBrowser.Model/Entities/ExtraType.cs | 20 + .../Entities/HardwareAccelerationType.cs | 49 + .../Entities/IHasProviderIds.cs | 16 + MediaBrowser.Model/Entities/IHasShares.cs | 14 + MediaBrowser.Model/Entities/ImageType.cs | 77 + MediaBrowser.Model/Entities/IsoType.cs | 18 + .../Entities/LibraryUpdateInfo.cs | 59 + MediaBrowser.Model/Entities/LocationType.cs | 28 + .../Entities/MediaAttachment.cs | 49 + MediaBrowser.Model/Entities/MediaStream.cs | 840 + .../Entities/MediaStreamType.cs | 38 + MediaBrowser.Model/Entities/MediaUrl.cs | 12 + MediaBrowser.Model/Entities/MetadataField.cs | 53 + .../Entities/MetadataProvider.cs | 94 + MediaBrowser.Model/Entities/ParentalRating.cs | 40 + .../Entities/ParentalRatingEntry.cs | 22 + .../Entities/ParentalRatingScore.cs | 32 + .../Entities/ParentalRatingSystem.cs | 28 + MediaBrowser.Model/Entities/PersonType.cs | 68 + .../Entities/PlaylistUserPermissions.cs | 30 + .../Entities/ProviderIdsExtensions.cs | 216 + MediaBrowser.Model/Entities/SeriesStatus.cs | 23 + .../Entities/TonemappingAlgorithm.cs | 49 + .../Entities/TonemappingMode.cs | 34 + .../Entities/TonemappingRange.cs | 24 + MediaBrowser.Model/Entities/TrailerType.cs | 13 + .../Entities/UserDataSaveReason.cs | 43 + MediaBrowser.Model/Entities/Video3DFormat.cs | 13 + MediaBrowser.Model/Entities/VideoType.cs | 28 + .../Entities/VirtualFolderInfo.cs | 58 + .../Extensions/ContainerHelper.cs | 150 + .../Extensions/EnumerableExtensions.cs | 57 + .../Extensions/LibraryOptionsExtension.cs | 34 + MediaBrowser.Model/Extensions/StringHelper.cs | 38 + .../Globalization/CountryInfo.cs | 33 + .../Globalization/CultureDto.cs | 58 + .../Globalization/ILocalizationManager.cs | 73 + .../Globalization/LocalizationOption.cs | 18 + MediaBrowser.Model/IO/AsyncFile.cs | 53 + MediaBrowser.Model/IO/FileSystemEntryInfo.cs | 39 + MediaBrowser.Model/IO/FileSystemEntryType.cs | 28 + MediaBrowser.Model/IO/FileSystemMetadata.cs | 58 + MediaBrowser.Model/IO/IFileSystem.cs | 244 + MediaBrowser.Model/IO/IODefaults.cs | 25 + MediaBrowser.Model/IO/IShortcutHandler.cs | 27 + MediaBrowser.Model/IO/IStreamHelper.cs | 18 + MediaBrowser.Model/Library/PlayAccess.cs | 10 + MediaBrowser.Model/Library/UserViewQuery.cs | 37 + MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs | 130 + .../LiveTv/ChannelMappingOptionsDto.cs | 31 + MediaBrowser.Model/LiveTv/ChannelType.cs | 18 + MediaBrowser.Model/LiveTv/DayPattern.cs | 11 + MediaBrowser.Model/LiveTv/GuideInfo.cs | 22 + .../LiveTv/ListingsProviderInfo.cs | 58 + .../LiveTv/LiveTvChannelQuery.cs | 111 + MediaBrowser.Model/LiveTv/LiveTvInfo.cs | 33 + MediaBrowser.Model/LiveTv/LiveTvOptions.cs | 48 + .../LiveTv/LiveTvServiceInfo.cs | 62 + .../LiveTv/LiveTvServiceStatus.cs | 10 + MediaBrowser.Model/LiveTv/ProgramAudio.cs | 14 + MediaBrowser.Model/LiveTv/RecordingQuery.cs | 94 + MediaBrowser.Model/LiveTv/RecordingStatus.cs | 15 + .../LiveTv/SeriesTimerInfoDto.cs | 94 + MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs | 21 + MediaBrowser.Model/LiveTv/TimerInfoDto.cs | 45 + MediaBrowser.Model/LiveTv/TimerQuery.cs | 26 + .../LiveTv/TunerChannelMapping.cs | 16 + MediaBrowser.Model/LiveTv/TunerHostInfo.cs | 50 + MediaBrowser.Model/Lyrics/LyricDto.cs | 19 + MediaBrowser.Model/Lyrics/LyricFile.cs | 28 + MediaBrowser.Model/Lyrics/LyricLine.cs | 37 + MediaBrowser.Model/Lyrics/LyricLineCue.cs | 42 + MediaBrowser.Model/Lyrics/LyricMetadata.cs | 57 + MediaBrowser.Model/Lyrics/LyricResponse.cs | 19 + .../Lyrics/LyricSearchRequest.cs | 64 + .../Lyrics/RemoteLyricInfoDto.cs | 22 + MediaBrowser.Model/Lyrics/UploadLyricDto.cs | 16 + MediaBrowser.Model/MediaBrowser.Model.csproj | 70 + MediaBrowser.Model/MediaInfo/AudioCodec.cs | 34 + .../MediaInfo/AudioIndexSource.cs | 30 + .../MediaInfo/BlurayDiscInfo.cs | 41 + .../MediaInfo/IBlurayExaminer.cs | 14 + .../MediaInfo/LiveStreamRequest.cs | 48 + .../MediaInfo/LiveStreamResponse.cs | 16 + MediaBrowser.Model/MediaInfo/MediaInfo.cs | 86 + MediaBrowser.Model/MediaInfo/MediaProtocol.cs | 15 + .../MediaInfo/PlaybackInfoResponse.cs | 39 + .../MediaInfo/SubtitleFormat.cs | 15 + .../MediaInfo/SubtitleTrackEvent.cs | 21 + .../MediaInfo/SubtitleTrackInfo.cs | 17 + .../MediaInfo/TransportStreamTimestamp.cs | 11 + .../MediaSegments/MediaSegmentDto.cs | 37 + .../MediaSegmentGenerationRequest.cs | 22 + MediaBrowser.Model/Net/EndPointInfo.cs | 11 + MediaBrowser.Model/Net/IPData.cs | 78 + MediaBrowser.Model/Net/ISocketFactory.cs | 17 + MediaBrowser.Model/Net/MimeTypes.cs | 202 + .../Net/PublishedServerUriOverride.cs | 42 + .../Notifications/NotificationType.cs | 23 + .../Playlists/PlaylistCreationRequest.cs | 42 + .../Playlists/PlaylistCreationResult.cs | 14 + .../Playlists/PlaylistUpdateRequest.cs | 41 + .../Playlists/PlaylistUserUpdateRequest.cs | 24 + .../Plugins/BasePluginConfiguration.cs | 9 + MediaBrowser.Model/Plugins/IHasWebPages.cs | 11 + MediaBrowser.Model/Plugins/PluginInfo.cs | 67 + MediaBrowser.Model/Plugins/PluginPageInfo.cs | 38 + MediaBrowser.Model/Plugins/PluginStatus.cs | 52 + MediaBrowser.Model/Properties/AssemblyInfo.cs | 23 + .../Providers/ExternalIdInfo.cs | 44 + .../Providers/ExternalIdMediaType.cs | 81 + MediaBrowser.Model/Providers/ExternalUrl.cs | 20 + .../Providers/ImageProviderInfo.cs | 32 + .../Providers/LyricProviderInfo.cs | 17 + .../Providers/RemoteImageInfo.cs | 71 + .../Providers/RemoteImageQuery.cs | 22 + .../Providers/RemoteImageResult.cs | 27 + .../Providers/RemoteLyricInfo.cs | 29 + .../Providers/RemoteSearchResult.cs | 54 + .../Providers/RemoteSubtitleInfo.cs | 42 + .../Providers/SubtitleOptions.cs | 36 + .../Providers/SubtitleProviderInfo.cs | 12 + .../Querying/AllThemeMediaResult.cs | 23 + MediaBrowser.Model/Querying/ItemFields.cs | 253 + MediaBrowser.Model/Querying/ItemFilter.cs | 53 + .../Querying/LatestItemsQuery.cs | 85 + MediaBrowser.Model/Querying/NextUpQuery.cs | 72 + MediaBrowser.Model/Querying/QueryFilters.cs | 21 + .../Querying/QueryFiltersLegacy.cs | 26 + MediaBrowser.Model/Querying/QueryResult.cs | 60 + .../Querying/ThemeMediaResult.cs | 17 + .../QuickConnect/QuickConnectResult.cs | 78 + MediaBrowser.Model/Search/SearchHint.cs | 200 + MediaBrowser.Model/Search/SearchHintResult.cs | 33 + MediaBrowser.Model/Search/SearchQuery.cs | 75 + .../Serialization/IXmlSerializer.cs | 49 + MediaBrowser.Model/Session/BrowseRequest.cs | 29 + .../Session/ClientCapabilities.cs | 34 + MediaBrowser.Model/Session/GeneralCommand.cs | 27 + .../Session/GeneralCommandType.cs | 54 + MediaBrowser.Model/Session/MessageCommand.cs | 17 + MediaBrowser.Model/Session/PlayCommand.cs | 33 + MediaBrowser.Model/Session/PlayMethod.cs | 11 + MediaBrowser.Model/Session/PlayRequest.cs | 45 + MediaBrowser.Model/Session/PlaybackOrder.cs | 18 + .../Session/PlaybackProgressInfo.cs | 120 + .../Session/PlaybackStartInfo.cs | 9 + .../Session/PlaybackStopInfo.cs | 68 + MediaBrowser.Model/Session/PlayerStateInfo.cs | 80 + .../Session/PlaystateCommand.cs | 53 + .../Session/PlaystateRequest.cs | 17 + MediaBrowser.Model/Session/QueueItem.cs | 13 + MediaBrowser.Model/Session/RepeatMode.cs | 11 + .../Session/SessionMessageType.cs | 50 + MediaBrowser.Model/Session/SessionUserInfo.cs | 23 + MediaBrowser.Model/Session/TranscodeReason.cs | 47 + MediaBrowser.Model/Session/TranscodingInfo.cs | 76 + .../Session/UserDataChangeInfo.cs | 23 + MediaBrowser.Model/Subtitles/FontFile.cs | 34 + MediaBrowser.Model/SyncPlay/GroupInfoDto.cs | 58 + MediaBrowser.Model/SyncPlay/GroupQueueMode.cs | 18 + .../SyncPlay/GroupRepeatMode.cs | 23 + .../SyncPlay/GroupShuffleMode.cs | 18 + MediaBrowser.Model/SyncPlay/GroupStateType.cs | 28 + .../SyncPlay/GroupStateUpdate.cs | 31 + MediaBrowser.Model/SyncPlay/GroupUpdate.cs | 39 + .../SyncPlay/GroupUpdateType.cs | 53 + .../SyncPlay/PlayQueueUpdate.cs | 82 + .../SyncPlay/PlayQueueUpdateReason.cs | 58 + .../SyncPlay/PlaybackRequestType.cs | 93 + MediaBrowser.Model/SyncPlay/RequestType.cs | 33 + MediaBrowser.Model/SyncPlay/SendCommand.cs | 65 + .../SyncPlay/SendCommandType.cs | 28 + .../SyncPlay/SyncPlayBroadcastType.cs | 28 + .../SyncPlayGroupDoesNotExistUpdate.cs | 21 + .../SyncPlay/SyncPlayGroupJoinedUpdate.cs | 21 + .../SyncPlay/SyncPlayGroupLeftUpdate.cs | 21 + .../SyncPlayLibraryAccessDeniedUpdate.cs | 21 + .../SyncPlay/SyncPlayNotInGroupUpdate.cs | 21 + .../SyncPlay/SyncPlayPlayQueueUpdate.cs | 21 + .../SyncPlay/SyncPlayQueueItem.cs | 31 + .../SyncPlay/SyncPlayStateUpdate.cs | 21 + .../SyncPlay/SyncPlayUserJoinedUpdate.cs | 21 + .../SyncPlay/SyncPlayUserLeftUpdate.cs | 21 + .../SyncPlay/UtcTimeResponse.cs | 33 + .../System/CastReceiverApplication.cs | 17 + .../System/FolderStorageInfo.cs | 32 + .../System/LibraryStorageInfo.cs | 25 + MediaBrowser.Model/System/LogFile.cs | 33 + MediaBrowser.Model/System/PublicSystemInfo.cs | 55 + MediaBrowser.Model/System/SystemInfo.cs | 144 + .../System/SystemStorageInfo.cs | 56 + .../Tasks/IConfigurableScheduledTask.cs | 26 + MediaBrowser.Model/Tasks/IScheduledTask.cs | 50 + .../Tasks/IScheduledTaskWorker.cs | 77 + MediaBrowser.Model/Tasks/ITaskManager.cs | 107 + MediaBrowser.Model/Tasks/ITaskTrigger.cs | 35 + .../Tasks/ScheduledTaskHelpers.cs | 41 + .../Tasks/TaskCompletionEventArgs.cs | 33 + .../Tasks/TaskCompletionStatus.cs | 28 + MediaBrowser.Model/Tasks/TaskInfo.cs | 80 + MediaBrowser.Model/Tasks/TaskOptions.cs | 14 + MediaBrowser.Model/Tasks/TaskResult.cs | 59 + MediaBrowser.Model/Tasks/TaskState.cs | 23 + MediaBrowser.Model/Tasks/TaskTriggerInfo.cs | 41 + .../Tasks/TaskTriggerInfoType.cs | 28 + .../Updates/InstallationInfo.cs | 56 + MediaBrowser.Model/Updates/PackageInfo.cs | 83 + MediaBrowser.Model/Updates/RepositoryInfo.cs | 26 + MediaBrowser.Model/Updates/VersionInfo.cs | 77 + .../Users/ForgotPasswordAction.cs | 15 + .../Users/ForgotPasswordResult.cs | 28 + MediaBrowser.Model/Users/PinRedeemResult.cs | 21 + MediaBrowser.Model/Users/UserPolicy.cs | 199 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../net10.0/MediaBrowser.Model.assets.cache | Bin 0 -> 7818 bytes ...owser.Model.csproj.AssemblyReference.cache | Bin 0 -> 3805 bytes .../MimeTypes/2.5.2/MimeTypes.Nullable.cs | 1287 ++ .../MimeTypes/2.5.2/MimeTypes.Nullable.cs | 1287 ++ ...ediaBrowser.Model.csproj.nuget.dgspec.json | 2254 ++ .../MediaBrowser.Model.csproj.nuget.g.props | 26 + .../MediaBrowser.Model.csproj.nuget.g.targets | 6 + MediaBrowser.Model/obj/project.assets.json | 1271 ++ MediaBrowser.Model/obj/project.nuget.cache | 26 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../Books/AudioBookMetadataService.cs | 64 + .../Books/BookMetadataService.cs | 51 + .../OpenPackagingFormat/EpubImageProvider.cs | 120 + .../Books/OpenPackagingFormat/EpubProvider.cs | 100 + .../Books/OpenPackagingFormat/EpubUtils.cs | 35 + .../Books/OpenPackagingFormat/OpfProvider.cs | 94 + .../Books/OpenPackagingFormat/OpfReader.cs | 329 + .../BoxSets/BoxSetMetadataService.cs | 93 + .../Channels/ChannelMetadataService.cs | 39 + .../CollectionFolderMetadataService.cs | 39 + .../Folders/FolderMetadataService.cs | 43 + .../Folders/UserViewMetadataService.cs | 39 + .../Genres/GenreMetadataService.cs | 39 + .../LiveTv/LiveTvMetadataService.cs | 39 + .../Lyric/LrcLyricParser.cs | 122 + MediaBrowser.Providers/Lyric/LyricManager.cs | 468 + .../Lyric/LyricScheduledTask.cs | 171 + .../Lyric/TxtLyricParser.cs | 45 + MediaBrowser.Providers/Manager/ImageSaver.cs | 718 + .../Manager/ItemImageProvider.cs | 749 + .../Manager/MetadataService.cs | 1315 ++ .../Manager/ProviderManager.cs | 1172 ++ .../Manager/RefreshResult.cs | 15 + .../MediaBrowser.Providers.csproj | 62 + .../MediaInfo/AudioFileProber.cs | 592 + .../MediaInfo/AudioImageProvider.cs | 156 + .../MediaInfo/AudioResolver.cs | 40 + .../MediaInfo/EmbeddedImageProvider.cs | 245 + .../MediaInfo/FFProbeVideoInfo.cs | 674 + .../MediaInfo/LyricResolver.cs | 39 + .../MediaInfo/MediaInfoResolver.cs | 347 + .../MediaInfo/ProbeProvider.cs | 299 + .../MediaInfo/SubtitleDownloader.cs | 213 + .../MediaInfo/SubtitleResolver.cs | 40 + .../MediaInfo/SubtitleScheduledTask.cs | 223 + .../MediaInfo/VideoImageProvider.cs | 134 + .../Movies/ImdbExternalId.cs | 36 + .../Movies/ImdbExternalUrlProvider.cs | 32 + .../Movies/ImdbPersonExternalId.cs | 24 + .../Movies/MovieMetadataService.cs | 54 + .../Movies/TrailerMetadataService.cs | 56 + .../Music/AlbumInfoExtensions.cs | 81 + .../Music/AlbumMetadataService.cs | 266 + .../Music/ArtistMetadataService.cs | 56 + .../Music/AudioMetadataService.cs | 84 + MediaBrowser.Providers/Music/ImvdbId.cs | 25 + .../Music/MusicVideoMetadataService.cs | 70 + .../MusicGenres/MusicGenreMetadataService.cs | 39 + .../People/PersonMetadataService.cs | 39 + .../Photos/PhotoAlbumMetadataService.cs | 39 + .../Photos/PhotoMetadataService.cs | 39 + .../Playlists/PlaylistItemsProvider.cs | 227 + .../Playlists/PlaylistMetadataService.cs | 88 + .../Plugins/AudioDb/AudioDbAlbumExternalId.cs | 24 + .../AudioDbAlbumExternalUrlProvider.cs | 31 + .../AudioDb/AudioDbAlbumImageProvider.cs | 111 + .../Plugins/AudioDb/AudioDbAlbumProvider.cs | 303 + .../AudioDb/AudioDbArtistExternalId.cs | 24 + .../AudioDbArtistExternalUrlProvider.cs | 32 + .../AudioDb/AudioDbArtistImageProvider.cs | 155 + .../Plugins/AudioDb/AudioDbArtistProvider.cs | 290 + .../AudioDb/AudioDbOtherAlbumExternalId.cs | 24 + .../AudioDb/AudioDbOtherArtistExternalId.cs | 24 + .../Configuration/PluginConfiguration.cs | 11 + .../Plugins/AudioDb/Configuration/config.html | 54 + .../Plugins/AudioDb/Plugin.cs | 41 + .../Configuration/PluginConfiguration.cs | 57 + .../MusicBrainz/Configuration/config.html | 78 + .../MusicBrainzAlbumArtistExternalId.cs | 24 + ...sicBrainzAlbumArtistExternalUrlProvider.cs | 28 + .../MusicBrainz/MusicBrainzAlbumExternalId.cs | 24 + .../MusicBrainzAlbumExternalUrlProvider.cs | 28 + .../MusicBrainz/MusicBrainzAlbumProvider.cs | 307 + .../MusicBrainzArtistExternalId.cs | 24 + .../MusicBrainzArtistExternalUrlProvider.cs | 32 + .../MusicBrainz/MusicBrainzArtistProvider.cs | 186 + .../MusicBrainzOtherArtistExternalId.cs | 24 + .../MusicBrainz/MusicBrainzRecordingId.cs | 24 + .../MusicBrainzReleaseGroupExternalId.cs | 24 + ...icBrainzReleaseGroupExternalUrlProvider.cs | 28 + .../MusicBrainzTrackExternalUrlProvider.cs | 28 + .../Plugins/MusicBrainz/MusicBrainzTrackId.cs | 24 + .../Plugins/MusicBrainz/Plugin.cs | 64 + .../Omdb/Configuration/PluginConfiguration.cs | 11 + .../Plugins/Omdb/Configuration/config.html | 53 + .../JsonOmdbNotAvailableInt32Converter.cs | 44 + .../JsonOmdbNotAvailableStringConverter.cs | 42 + .../Plugins/Omdb/OmdbEpisodeProvider.cs | 79 + .../Plugins/Omdb/OmdbImageProvider.cs | 80 + .../Plugins/Omdb/OmdbItemProvider.cs | 312 + .../Plugins/Omdb/OmdbProvider.cs | 570 + MediaBrowser.Providers/Plugins/Omdb/Plugin.cs | 41 + .../Configuration/PluginConfiguration.cs | 35 + .../StudioImages/Configuration/config.html | 59 + .../Plugins/StudioImages/Plugin.cs | 63 + .../StudioImages/StudiosImageProvider.cs | 192 + .../Plugins/Tmdb/Api/TmdbController.cs | 41 + .../Tmdb/BoxSets/TmdbBoxSetExternalId.cs | 29 + .../Tmdb/BoxSets/TmdbBoxSetImageProvider.cs | 92 + .../Tmdb/BoxSets/TmdbBoxSetProvider.cs | 144 + .../Tmdb/Configuration/PluginConfiguration.cs | 81 + .../Plugins/Tmdb/Configuration/config.html | 196 + .../Tmdb/Movies/TmdbMovieExternalId.cs | 35 + .../Tmdb/Movies/TmdbMovieImageProvider.cs | 113 + .../Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 378 + .../Tmdb/People/TmdbPersonExternalId.cs | 28 + .../Tmdb/People/TmdbPersonImageProvider.cs | 79 + .../Plugins/Tmdb/People/TmdbPersonProvider.cs | 144 + MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs | 60 + .../Tmdb/TV/TmdbEpisodeImageProvider.cs | 96 + .../Plugins/Tmdb/TV/TmdbEpisodeProvider.cs | 322 + .../Tmdb/TV/TmdbSeasonImageProvider.cs | 89 + .../Plugins/Tmdb/TV/TmdbSeasonProvider.cs | 171 + .../Plugins/Tmdb/TV/TmdbSeriesExternalId.cs | 28 + .../Tmdb/TV/TmdbSeriesImageProvider.cs | 97 + .../Plugins/Tmdb/TV/TmdbSeriesProvider.cs | 413 + .../Plugins/Tmdb/TmdbClientManager.cs | 705 + .../Plugins/Tmdb/TmdbExternalUrlProvider.cs | 98 + .../Plugins/Tmdb/TmdbUtils.cs | 212 + .../Properties/AssemblyInfo.cs | 23 + .../Studios/StudioMetadataService.cs | 39 + .../Subtitles/SubtitleManager.cs | 414 + .../TV/EpisodeMetadataService.cs | 113 + .../TV/SeasonMetadataService.cs | 117 + .../TV/SeriesMetadataService.cs | 322 + MediaBrowser.Providers/TV/Zap2ItExternalId.cs | 24 + .../TV/Zap2ItExternalUrlProvider.cs | 24 + .../Trickplay/TrickplayImagesTask.cs | 118 + .../Trickplay/TrickplayMoveImagesTask.cs | 119 + .../Trickplay/TrickplayProvider.cs | 121 + .../Videos/VideoMetadataService.cs | 43 + .../Years/YearMetadataService.cs | 39 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../MediaBrowser.Providers.assets.cache | Bin 0 -> 24725 bytes ...r.Providers.csproj.AssemblyReference.cache | Bin 0 -> 9112 bytes ...Browser.Providers.csproj.nuget.dgspec.json | 4794 +++++ ...ediaBrowser.Providers.csproj.nuget.g.props | 26 + ...iaBrowser.Providers.csproj.nuget.g.targets | 9 + .../obj/project.assets.json | 2458 +++ .../obj/project.nuget.cache | 52 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../NfoConfigurationExtension.cs | 15 + .../Configuration/NfoConfigurationFactory.cs | 24 + .../MediaBrowser.XbmcMetadata.csproj | 38 + MediaBrowser.XbmcMetadata/NfoUserDataSaver.cs | 87 + .../Parsers/BaseNfoParser.cs | 1019 + .../Parsers/EpisodeNfoParser.cs | 202 + .../Parsers/MovieNfoParser.cs | 160 + .../Parsers/SeasonNfoParser.cs | 59 + .../Parsers/SeriesNfoParser.cs | 105 + .../Parsers/SeriesNfoSeasonParser.cs | 60 + .../Properties/AssemblyInfo.cs | 23 + .../Providers/AlbumNfoProvider.cs | 63 + .../Providers/ArtistNfoProvider.cs | 63 + .../Providers/BaseNfoProvider.cs | 86 + .../Providers/BaseVideoNfoProvider.cs | 67 + .../Providers/EpisodeNfoProvider.cs | 67 + .../Providers/MovieNfoProvider.cs | 37 + .../Providers/MusicVideoNfoProvider.cs | 37 + .../Providers/SeasonNfoProvider.cs | 63 + .../Providers/SeriesNfoProvider.cs | 63 + .../Providers/SeriesNfoSeasonProvider.cs | 89 + .../Providers/VideoNfoProvider.cs | 37 + .../Savers/AlbumNfoSaver.cs | 121 + .../Savers/ArtistNfoSaver.cs | 106 + .../Savers/BaseNfoSaver.cs | 1040 + .../Savers/EpisodeNfoSaver.cs | 131 + .../Savers/MovieNfoSaver.cs | 138 + .../Savers/SeasonNfoSaver.cs | 85 + .../Savers/SeriesNfoSaver.cs | 106 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../MediaBrowser.XbmcMetadata.assets.cache | Bin 0 -> 16672 bytes ...bmcMetadata.csproj.AssemblyReference.cache | Bin 0 -> 4462 bytes ...wser.XbmcMetadata.csproj.nuget.dgspec.json | 4749 +++++ ...aBrowser.XbmcMetadata.csproj.nuget.g.props | 26 + ...rowser.XbmcMetadata.csproj.nuget.g.targets | 9 + .../obj/project.assets.json | 1875 ++ .../obj/project.nuget.cache | 38 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + README.md | 202 + SharedVersion.cs | 4 + bump_version | 65 + deployment/unraid/docker-templates/README.md | 15 + .../unraid/docker-templates/jellyfin.xml | 57 + fuzz/.gitignore | 1 + .../Emby.Server.Implementations.Fuzz.csproj | 25 + .../Program.cs | 71 + .../test1.txt | 1 + .../test1.txt | 1 + fuzz/Emby.Server.Implementations.Fuzz/fuzz.sh | 11 + .../Jellyfin.Api.Fuzz.csproj | 22 + fuzz/Jellyfin.Api.Fuzz/Program.cs | 33 + .../Testcases/UrlDecodeQueryFeature/test1.txt | 1 + fuzz/Jellyfin.Api.Fuzz/fuzz.sh | 11 + fuzz/README.md | 20 + global.json | 6 + jellyfin.code-workspace | 17 + nuget.config | 8 + src/Directory.Build.props | 21 + .../AnalyzerReleases.Shipped.md | 9 + .../AsyncDisposalPatternAnalyzer.cs | 82 + .../Jellyfin.CodeAnalysis.csproj | 17 + ...tandard,Version=v2.0.AssemblyAttributes.cs | 4 + .../Jellyfin.CodeAnalysis.AssemblyInfo.cs | 22 + ...yfin.CodeAnalysis.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 26 + .../Jellyfin.CodeAnalysis.assets.cache | Bin 0 -> 17768 bytes ...odeAnalysis.csproj.AssemblyReference.cache | Bin 0 -> 55871 bytes ...yfin.CodeAnalysis.csproj.nuget.dgspec.json | 205 + ...Jellyfin.CodeAnalysis.csproj.nuget.g.props | 27 + ...llyfin.CodeAnalysis.csproj.nuget.g.targets | 8 + .../obj/project.assets.json | 1852 ++ .../obj/project.nuget.cache | 28 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../DbConfiguration/CustomDatabaseOption.cs | 19 + .../DbConfiguration/CustomDatabaseOptions.cs | 32 + .../DatabaseConfigurationOptions.cs | 25 + .../DatabaseLockingBehaviorTypes.cs | 22 + .../Entities/AccessSchedule.cs | 62 + .../Entities/ActivityLog.cs | 123 + .../Entities/AncestorId.cs | 29 + .../Entities/AttachmentStreamInfo.cs | 49 + .../Entities/BaseItemEntity.cs | 190 + .../Entities/BaseItemExtraType.cs | 18 + .../Entities/BaseItemImageInfo.cs | 58 + .../Entities/BaseItemMetadataField.cs | 24 + .../Entities/BaseItemProvider.cs | 29 + .../Entities/BaseItemTrailerType.cs | 24 + .../Entities/Chapter.cs | 44 + .../Entities/CustomItemDisplayPreferences.cs | 80 + .../Entities/DisplayPreferences.cs | 150 + .../Entities/Group.cs | 66 + .../Entities/HomeSection.cs | 44 + .../Entities/ImageInfo.cs | 54 + .../Entities/ImageInfoImageType.cs | 76 + .../Entities/ItemDisplayPreferences.cs | 113 + .../Entities/ItemValue.cs | 37 + .../Entities/ItemValueMap.cs | 29 + .../Entities/ItemValueType.cs | 38 + .../Entities/KeyframeData.cs | 32 + .../Entities/Libraries/Artwork.cs | 64 + .../Entities/Libraries/Book.cs | 29 + .../Entities/Libraries/BookMetadata.cs | 34 + .../Entities/Libraries/Chapter.cs | 80 + .../Entities/Libraries/Collection.cs | 57 + .../Entities/Libraries/CollectionItem.cs | 64 + .../Entities/Libraries/Company.cs | 54 + .../Entities/Libraries/CompanyMetadata.cs | 59 + .../Entities/Libraries/CustomItem.cs | 29 + .../Entities/Libraries/CustomItemMetadata.cs | 17 + .../Entities/Libraries/Episode.cs | 34 + .../Entities/Libraries/EpisodeMetadata.cs | 49 + .../Entities/Libraries/Genre.cs | 50 + .../Entities/Libraries/ItemMetadata.cs | 141 + .../Entities/Libraries/Library.cs | 60 + .../Entities/Libraries/LibraryItem.cs | 55 + .../Entities/Libraries/MediaFile.cs | 72 + .../Entities/Libraries/MediaFileStream.cs | 50 + .../Entities/Libraries/MetadataProvider.cs | 53 + .../Entities/Libraries/MetadataProviderId.cs | 63 + .../Entities/Libraries/Movie.cs | 29 + .../Entities/Libraries/MovieMetadata.cs | 70 + .../Entities/Libraries/MusicAlbum.cs | 30 + .../Entities/Libraries/MusicAlbumMetadata.cs | 56 + .../Entities/Libraries/Person.cs | 89 + .../Entities/Libraries/PersonRole.cs | 80 + .../Entities/Libraries/Photo.cs | 29 + .../Entities/Libraries/PhotoMetadata.cs | 17 + .../Entities/Libraries/Rating.cs | 59 + .../Entities/Libraries/RatingSource.cs | 73 + .../Entities/Libraries/Release.cs | 67 + .../Entities/Libraries/Season.cs | 35 + .../Entities/Libraries/SeasonMetadata.cs | 29 + .../Entities/Libraries/Series.cs | 46 + .../Entities/Libraries/SeriesMetadata.cs | 70 + .../Entities/Libraries/Track.cs | 34 + .../Entities/Libraries/TrackMetadata.cs | 17 + .../Entities/MediaSegment.cs | 42 + .../Entities/MediaStreamInfo.cs | 104 + .../Entities/MediaStreamTypeEntity.cs | 37 + .../Entities/People.cs | 32 + .../Entities/PeopleBaseItemMap.cs | 44 + .../Entities/Permission.cs | 68 + .../Entities/Preference.cs | 68 + .../Entities/ProgramAudioEntity.cs | 37 + .../Entities/Security/ApiKey.cs | 56 + .../Entities/Security/Device.cs | 107 + .../Entities/Security/DeviceOptions.cs | 35 + .../Entities/TrickplayInfo.cs | 74 + .../Entities/User.cs | 340 + .../Entities/UserData.cs | 96 + .../Enums/ArtKind.cs | 32 + .../Enums/ChromecastVersion.cs | 17 + .../Enums/DynamicDayOfWeek.cs | 57 + .../Enums/HomeSectionType.cs | 57 + .../Enums/IndexingKind.cs | 22 + .../Enums/MediaFileKind.cs | 32 + .../Enums/MediaSegmentType.cs | 39 + .../Enums/PermissionKind.cs | 127 + .../Enums/PersonRoleType.cs | 67 + .../Enums/PreferenceKind.cs | 72 + .../Enums/ScrollDirection.cs | 17 + .../Enums/SortOrder.cs | 17 + .../Enums/SubtitlePlaybackMode.cs | 32 + .../Enums/SyncPlayUserAccessType.cs | 22 + .../Enums/ViewType.cs | 112 + .../IJellyfinDatabaseProvider.cs | 83 + .../Interfaces/IHasArtwork.cs | 16 + .../Interfaces/IHasCompanies.cs | 16 + .../Interfaces/IHasConcurrencyToken.cs | 17 + .../Interfaces/IHasPermissions.cs | 15 + .../Interfaces/IHasReleases.cs | 15 + .../Jellyfin.Database.Implementations.csproj | 39 + .../JellyfinDatabaseProviderKeyAttribute.cs | 29 + .../JellyfinDbContext.cs | 326 + .../JellyfinQueryHelperExtensions.cs | 194 + .../IEntityFrameworkCoreLockingBehavior.cs | 32 + .../Locking/NoLockBehavior.cs | 41 + .../Locking/OptimisticLockBehavior.cs | 163 + .../Locking/PessimisticLockBehavior.cs | 297 + .../ActivityLogConfiguration.cs | 17 + .../AncestorIdConfiguration.cs | 20 + .../ModelConfiguration/ApiKeyConfiguration.cs | 20 + .../AttachmentStreamInfoConfiguration.cs | 17 + .../BaseItemConfiguration.cs | 66 + .../BaseItemMetadataFieldConfiguration.cs | 18 + .../BaseItemProviderConfiguration.cs | 19 + .../BaseItemTrailerTypeConfiguration.cs | 18 + .../ChapterConfiguration.cs | 18 + ...stomItemDisplayPreferencesConfiguration.cs | 20 + .../ModelConfiguration/DeviceConfiguration.cs | 28 + .../DeviceOptionsConfiguration.cs | 20 + .../DisplayPreferencesConfiguration.cs | 25 + .../ItemValuesConfiguration.cs | 19 + .../ItemValuesMapConfiguration.cs | 19 + .../KeyframeDataConfiguration.cs | 18 + .../MediaStreamInfoConfiguration.cs | 21 + .../PeopleBaseItemMapConfiguration.cs | 21 + .../ModelConfiguration/PeopleConfiguration.cs | 19 + .../PermissionConfiguration.cs | 24 + .../PreferenceConfiguration.cs | 21 + .../TrickplayInfoConfiguration.cs | 18 + .../ModelConfiguration/UserConfiguration.cs | 55 + .../UserDataConfiguration.cs | 22 + .../ProgressablePartitionReporting.cs | 55 + .../QueryPartitionHelpers.cs | 215 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 35 + ...yfin.Database.Implementations.assets.cache | Bin 0 -> 41249 bytes ...ementations.csproj.AssemblyReference.cache | Bin 0 -> 7025 bytes ...e.Implementations.csproj.nuget.dgspec.json | 691 + ...abase.Implementations.csproj.nuget.g.props | 30 + ...ase.Implementations.csproj.nuget.g.targets | 10 + .../obj/project.assets.json | 3387 +++ .../obj/project.nuget.cache | 51 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../DoNotUseReturningClauseConvention.cs | 20 + .../Jellyfin.Database.Providers.Sqlite.csproj | 31 + .../Migrations/.gitattributes | 1 + .../20200514181226_AddActivityLog.Designer.cs | 72 + .../20200514181226_AddActivityLog.cs | 46 + .../20200613202153_AddUsers.Designer.cs | 312 + .../Migrations/20200613202153_AddUsers.cs | 197 + ...28005145_AddDisplayPreferences.Designer.cs | 459 + .../20200728005145_AddDisplayPreferences.cs | 132 + ...533_FixDisplayPreferencesIndex.Designer.cs | 461 + ...200905220533_FixDisplayPreferencesIndex.cs | 51 + ...004171403_AddMaxActiveSessions.Designer.cs | 464 + .../20201004171403_AddMaxActiveSessions.cs | 28 + ...55_AddCustomDisplayPreferences.Designer.cs | 522 + ...01204223655_AddCustomDisplayPreferences.cs | 108 + ...181425_AddIndexesAndCollations.Designer.cs | 535 + .../20210320181425_AddIndexesAndCollations.cs | 240 + ...110544_NullableCustomPrefValue.Designer.cs | 520 + .../20210407110544_NullableCustomPrefValue.cs | 35 + .../20210814002109_AddDevices.Designer.cs | 653 + .../Migrations/20210814002109_AddDevices.cs | 128 + ...ddIndexActivityLogsDateCreated.Designer.cs | 657 + ...2080052_AddIndexActivityLogsDateCreated.cs | 28 + ...30526173516_RemoveEasyPassword.Designer.cs | 650 + .../20230526173516_RemoveEasyPassword.cs | 164 + ...230626233818_AddTrickplayInfos.Designer.cs | 681 + .../20230626233818_AddTrickplayInfos.cs | 40 + ...0230923170422_UserCastReceiver.Designer.cs | 654 + .../20230923170422_UserCastReceiver.cs | 29 + ...0240729140605_AddMediaSegments.Designer.cs | 708 + .../20240729140605_AddMediaSegments.cs | 38 + ...rkSegmentProviderIdNonNullable.Designer.cs | 712 + ...082930_MarkSegmentProviderIdNonNullable.cs | 36 + ...41020103111_LibraryDbMigration.Designer.cs | 1607 ++ .../20241020103111_LibraryDbMigration.cs | 639 + ...41111131257_AddedCustomDataKey.Designer.cs | 1610 ++ .../20241111131257_AddedCustomDataKey.cs | 28 + ...11135439_AddedCustomDataKeyKey.Designer.cs | 1610 ++ .../20241111135439_AddedCustomDataKeyKey.cs | 54 + ...1112152323_FixAncestorIdConfig.Designer.cs | 1603 ++ .../20241112152323_FixAncestorIdConfig.cs | 49 + ...20241112232041_fixMediaStreams.Designer.cs | 1600 ++ .../20241112232041_fixMediaStreams.cs | 702 + ...0241112234144_FixMediaStreams2.Designer.cs | 1594 ++ .../20241112234144_FixMediaStreams2.cs | 144 + ...3133548_EnforceUniqueItemValue.Designer.cs | 1595 ++ .../20241113133548_EnforceUniqueItemValue.cs | 37 + .../20250202021306_FixedCollation.Designer.cs | 1594 ++ .../20250202021306_FixedCollation.cs | 40 + ...92455_MakeStartEndDateNullable.Designer.cs | 1595 ++ ...20250204092455_MakeStartEndDateNullable.cs | 55 + .../20250214031148_ChannelIdGuid.Designer.cs | 1595 ++ .../20250214031148_ChannelIdGuid.cs | 22 + ...nheritedParentalRatingSubValue.Designer.cs | 1658 ++ ...5026_AddInheritedParentalRatingSubValue.cs | 48 + ...20250327101120_AddKeyframeData.Designer.cs | 1681 ++ .../20250327101120_AddKeyframeData.cs | 41 + ...0250327171413_AddHdr10PlusFlag.Designer.cs | 1655 ++ .../20250327171413_AddHdr10PlusFlag.cs | 28 + ...1182844_FixAttachmentMigration.Designer.cs | 1657 ++ .../20250331182844_FixAttachmentMigration.cs | 36 + .../20250401142247_FixAncestors.Designer.cs | 1658 ++ .../Migrations/20250401142247_FixAncestors.cs | 20 + ...405075612_FixItemValuesIndices.Designer.cs | 1694 ++ .../20250405075612_FixItemValuesIndices.cs | 47 + ..._DetachUserDataInsteadOfDelete.Designer.cs | 1693 ++ ...609115616_DetachUserDataInsteadOfDelete.cs | 39 + ...mImageInfoDateModifiedNullable.Designer.cs | 1709 ++ ...2_BaseItemImageInfoDateModifiedNullable.cs | 37 + ...0250714044826_ResetJournalMode.Designer.cs | 1709 ++ .../20250714044826_ResetJournalMode.cs | 22 + ...ildRelationBaseItemWithCascade.Designer.cs | 1721 ++ ...rParentChildRelationBaseItemWithCascade.cs | 52 + ...50925203415_ExtendPeopleMapKey.Designer.cs | 1721 ++ .../20250925203415_ExtendPeopleMapKey.cs | 54 + .../Migrations/JellyfinDbModelSnapshot.cs | 1718 ++ .../SqliteDesignTimeJellyfinDbFactory.cs | 27 + .../ModelBuilderExtensions.cs | 47 + .../PragmaConnectionInterceptor.cs | 108 + .../Properties/AssemblyInfo.cs | 23 + .../SqliteDatabaseProvider.cs | 208 + .../DateTimeKindValueConverter.cs | 21 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 35 + ...fin.Database.Providers.Sqlite.assets.cache | Bin 0 -> 52298 bytes ...ders.Sqlite.csproj.AssemblyReference.cache | Bin 0 -> 6707 bytes ....Providers.Sqlite.csproj.nuget.dgspec.json | 3354 +++ ...base.Providers.Sqlite.csproj.nuget.g.props | 30 + ...se.Providers.Sqlite.csproj.nuget.g.targets | 11 + .../obj/project.assets.json | 3989 ++++ .../obj/project.nuget.cache | 62 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + src/Jellyfin.Database/readme.md | 25 + .../Jellyfin.Drawing.Skia.csproj | 36 + .../PercentPlayedDrawer.cs | 35 + .../Properties/AssemblyInfo.cs | 21 + src/Jellyfin.Drawing.Skia/SkiaEncoder.cs | 820 + src/Jellyfin.Drawing.Skia/SkiaExtensions.cs | 58 + src/Jellyfin.Drawing.Skia/SkiaHelper.cs | 52 + .../SplashscreenBuilder.cs | 174 + .../StripCollageBuilder.cs | 311 + .../UnplayedCountIndicator.cs | 65 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../Jellyfin.Drawing.Skia.assets.cache | Bin 0 -> 28783 bytes ...rawing.Skia.csproj.AssemblyReference.cache | Bin 0 -> 8244 bytes ...yfin.Drawing.Skia.csproj.nuget.dgspec.json | 4790 +++++ ...Jellyfin.Drawing.Skia.csproj.nuget.g.props | 26 + ...llyfin.Drawing.Skia.csproj.nuget.g.targets | 9 + .../obj/project.assets.json | 2629 +++ .../obj/project.nuget.cache | 54 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + src/Jellyfin.Drawing/ImageProcessor.cs | 551 + src/Jellyfin.Drawing/Jellyfin.Drawing.csproj | 28 + src/Jellyfin.Drawing/NullImageEncoder.cs | 63 + .../Properties/AssemblyInfo.cs | 30 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../net10.0/Jellyfin.Drawing.assets.cache | Bin 0 -> 17124 bytes ...fin.Drawing.csproj.AssemblyReference.cache | Bin 0 -> 4882 bytes .../Jellyfin.Drawing.csproj.nuget.dgspec.json | 4757 +++++ .../obj/Jellyfin.Drawing.csproj.nuget.g.props | 26 + .../Jellyfin.Drawing.csproj.nuget.g.targets | 9 + src/Jellyfin.Drawing/obj/project.assets.json | 1925 ++ src/Jellyfin.Drawing/obj/project.nuget.cache | 39 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + src/Jellyfin.Extensions/CopyToExtensions.cs | 26 + .../DictionaryExtensions.cs | 45 + .../EnumerableExtensions.cs | 77 + src/Jellyfin.Extensions/FileHelper.cs | 20 + .../FormattingStreamWriter.cs | 38 + src/Jellyfin.Extensions/GuidExtensions.cs | 26 + .../Jellyfin.Extensions.csproj | 37 + .../Converters/JsonBoolNumberConverter.cs | 30 + .../Converters/JsonBoolStringConverter.cs | 33 + .../JsonCommaDelimitedCollectionConverter.cs | 19 + ...ommaDelimitedCollectionConverterFactory.cs | 31 + .../Json/Converters/JsonDateTimeConverter.cs | 34 + .../JsonDefaultStringEnumConverter.cs | 49 + .../JsonDefaultStringEnumConverterFactory.cs | 31 + .../JsonDelimitedCollectionConverter.cs | 76 + .../Json/Converters/JsonFlagEnumConverter.cs | 36 + .../JsonFlagEnumConverterFactory.cs | 24 + .../Json/Converters/JsonGuidConverter.cs | 30 + .../Converters/JsonNullableGuidConverter.cs | 31 + .../Converters/JsonNullableStructConverter.cs | 30 + .../JsonNullableStructConverterFactory.cs | 27 + .../JsonPipeDelimitedCollectionConverter.cs | 19 + ...PipeDelimitedCollectionConverterFactory.cs | 31 + .../Json/Converters/JsonStringConverter.cs | 30 + .../Json/Converters/JsonVersionConverter.cs | 23 + src/Jellyfin.Extensions/Json/JsonDefaults.cs | 93 + .../Json/Utf8JsonExtensions.cs | 27 + .../ReadOnlyListExtension.cs | 77 + src/Jellyfin.Extensions/ShuffleExtensions.cs | 39 + .../SplitStringExtensions.cs | 115 + src/Jellyfin.Extensions/StreamExtensions.cs | 64 + .../StringBuilderExtensions.cs | 35 + src/Jellyfin.Extensions/StringExtensions.cs | 152 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../net10.0/Jellyfin.Extensions.assets.cache | Bin 0 -> 7822 bytes ....Extensions.csproj.AssemblyReference.cache | Bin 0 -> 4085 bytes ...llyfin.Extensions.csproj.nuget.dgspec.json | 680 + .../Jellyfin.Extensions.csproj.nuget.g.props | 25 + ...Jellyfin.Extensions.csproj.nuget.g.targets | 6 + .../obj/project.assets.json | 986 + .../obj/project.nuget.cache | 24 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../ChannelDynamicMediaSourceProvider.cs | 43 + .../Channels/ChannelImageProvider.cs | 64 + .../Channels/ChannelManager.cs | 1215 ++ .../Channels/ChannelPostScanTask.cs | 100 + .../Channels/RefreshChannelsScheduledTask.cs | 87 + .../LiveTvConfigurationExtensions.cs | 27 + .../LiveTvConfigurationFactory.cs | 24 + src/Jellyfin.LiveTv/DefaultLiveTvService.cs | 999 + .../LiveTvServiceCollectionExtensions.cs | 46 + src/Jellyfin.LiveTv/Guide/GuideManager.cs | 761 + .../Guide/RefreshGuideScheduledTask.cs | 74 + src/Jellyfin.LiveTv/IO/DirectRecorder.cs | 118 + src/Jellyfin.LiveTv/IO/EncodedRecorder.cs | 338 + src/Jellyfin.LiveTv/IO/ExclusiveLiveStream.cs | 61 + src/Jellyfin.LiveTv/IO/IRecorder.cs | 27 + src/Jellyfin.LiveTv/IO/StreamHelper.cs | 120 + src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj | 23 + .../Listings/EpgChannelData.cs | 54 + .../Listings/ListingsManager.cs | 464 + .../Listings/SchedulesDirect.cs | 814 + .../SchedulesDirectDtos/BroadcasterDto.cs | 34 + .../SchedulesDirectDtos/CaptionDto.cs | 22 + .../Listings/SchedulesDirectDtos/CastDto.cs | 46 + .../SchedulesDirectDtos/ChannelDto.cs | 30 + .../SchedulesDirectDtos/ContentRatingDto.cs | 22 + .../Listings/SchedulesDirectDtos/CrewDto.cs | 40 + .../Listings/SchedulesDirectDtos/DayDto.cs | 30 + .../SchedulesDirectDtos/Description1000Dto.cs | 22 + .../SchedulesDirectDtos/Description100Dto.cs | 22 + .../DescriptionsProgramDto.cs | 24 + .../SchedulesDirectDtos/EventDetailsDto.cs | 16 + .../SchedulesDirectDtos/GracenoteDto.cs | 22 + .../SchedulesDirectDtos/HeadendsDto.cs | 36 + .../SchedulesDirectDtos/ImageDataDto.cs | 70 + .../Listings/SchedulesDirectDtos/LineupDto.cs | 46 + .../SchedulesDirectDtos/LineupsDto.cs | 36 + .../Listings/SchedulesDirectDtos/LogoDto.cs | 34 + .../Listings/SchedulesDirectDtos/MapDto.cs | 58 + .../SchedulesDirectDtos/MetadataDto.cs | 28 + .../MetadataProgramsDto.cs | 16 + .../MetadataScheduleDto.cs | 41 + .../Listings/SchedulesDirectDtos/MovieDto.cs | 30 + .../SchedulesDirectDtos/MultipartDto.cs | 22 + .../SchedulesDirectDtos/ProgramDetailsDto.cs | 156 + .../SchedulesDirectDtos/ProgramDto.cs | 90 + .../SchedulesDirectDtos/QualityRatingDto.cs | 40 + .../Listings/SchedulesDirectDtos/RatingDto.cs | 22 + .../SchedulesDirectDtos/RecommendationDto.cs | 22 + .../RequestScheduleForChannelDto.cs | 24 + .../SchedulesDirectDtos/ShowImagesDto.cs | 24 + .../SchedulesDirectDtos/StationDto.cs | 66 + .../Listings/SchedulesDirectDtos/TitleDto.cs | 16 + .../Listings/SchedulesDirectDtos/TokenDto.cs | 47 + .../Listings/XmlTvListingsProvider.cs | 269 + src/Jellyfin.LiveTv/LiveTvDtoService.cs | 549 + src/Jellyfin.LiveTv/LiveTvManager.cs | 1292 ++ .../LiveTvMediaSourceProvider.cs | 332 + .../Recordings/RecordingHelper.cs | 76 + .../Recordings/RecordingNotifier.cs | 97 + .../Recordings/RecordingsHost.cs | 37 + .../Recordings/RecordingsManager.cs | 838 + .../Recordings/RecordingsMetadataManager.cs | 496 + .../Timers/ItemDataProvider.cs | 156 + .../Timers/SeriesTimerManager.cs | 29 + src/Jellyfin.LiveTv/Timers/TimerManager.cs | 172 + .../TunerHosts/BaseTunerHost.cs | 232 + .../TunerHosts/HdHomerun/Channels.cs | 23 + .../TunerHosts/HdHomerun/DiscoverResponse.cs | 42 + .../HdHomerun/HdHomerunChannelCommands.cs | 35 + .../TunerHosts/HdHomerun/HdHomerunHost.cs | 557 + .../TunerHosts/HdHomerun/HdHomerunManager.cs | 351 + .../HdHomerun/HdHomerunUdpStream.cs | 219 + .../HdHomerun/IHdHomerunChannelCommands.cs | 11 + .../LegacyHdHomerunChannelCommands.cs | 40 + src/Jellyfin.LiveTv/TunerHosts/LiveStream.cs | 176 + .../TunerHosts/M3UTunerHost.cs | 218 + src/Jellyfin.LiveTv/TunerHosts/M3uParser.cs | 325 + .../TunerHosts/SharedHttpStream.cs | 135 + .../TunerHosts/TunerHostManager.cs | 174 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + .../net10.0/Jellyfin.LiveTv.AssemblyInfo.cs | 23 + .../Jellyfin.LiveTv.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../net10.0/Jellyfin.LiveTv.assets.cache | Bin 0 -> 17674 bytes ...yfin.LiveTv.csproj.AssemblyReference.cache | Bin 0 -> 5119 bytes .../Jellyfin.LiveTv.csproj.nuget.dgspec.json | 4762 +++++ .../obj/Jellyfin.LiveTv.csproj.nuget.g.props | 26 + .../Jellyfin.LiveTv.csproj.nuget.g.targets | 9 + src/Jellyfin.LiveTv/obj/project.assets.json | 1959 ++ src/Jellyfin.LiveTv/obj/project.nuget.cache | 40 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../Cache/CacheDecorator.cs | 61 + ...aEncodingHlsServiceCollectionExtensions.cs | 36 + .../Extractors/FfProbeKeyframeExtractor.cs | 58 + .../Extractors/IKeyframeExtractor.cs | 25 + .../Extractors/MatroskaKeyframeExtractor.cs | 48 + .../Jellyfin.MediaEncoding.Hls.csproj | 22 + .../Playlist/CreateMainPlaylistRequest.cs | 72 + .../Playlist/DynamicHlsPlaylistGenerator.cs | 210 + .../Playlist/IDynamicHlsPlaylistGenerator.cs | 14 + .../KeyframeExtractionScheduledTask.cs | 109 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ...Jellyfin.MediaEncoding.Hls.AssemblyInfo.cs | 23 + ...MediaEncoding.Hls.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../Jellyfin.MediaEncoding.Hls.assets.cache | Bin 0 -> 16451 bytes ...ncoding.Hls.csproj.AssemblyReference.cache | Bin 0 -> 4462 bytes ...MediaEncoding.Hls.csproj.nuget.dgspec.json | 4755 +++++ ...fin.MediaEncoding.Hls.csproj.nuget.g.props | 26 + ...n.MediaEncoding.Hls.csproj.nuget.g.targets | 9 + .../obj/project.assets.json | 1883 ++ .../obj/project.nuget.cache | 38 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../FfProbe/FfProbeKeyframeExtractor.cs | 131 + .../FfTool/FfToolKeyframeExtractor.cs | 17 + .../Jellyfin.MediaEncoding.Keyframes.csproj | 31 + .../KeyframeData.cs | 30 + .../Extensions/EbmlReaderExtensions.cs | 177 + .../Matroska/MatroskaConstants.cs | 30 + .../Matroska/MatroskaKeyframeExtractor.cs | 87 + .../Matroska/Models/Info.cs | 28 + .../Matroska/Models/SeekHead.cs | 35 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ...in.MediaEncoding.Keyframes.AssemblyInfo.cs | 24 + ...ncoding.Keyframes.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + ...lyfin.MediaEncoding.Keyframes.assets.cache | Bin 0 -> 2936 bytes ...g.Keyframes.csproj.AssemblyReference.cache | Bin 0 -> 231 bytes ...ncoding.Keyframes.csproj.nuget.dgspec.json | 672 + ...diaEncoding.Keyframes.csproj.nuget.g.props | 25 + ...aEncoding.Keyframes.csproj.nuget.g.targets | 6 + .../obj/project.assets.json | 711 + .../obj/project.nuget.cache | 16 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + src/Jellyfin.Networking/AutoDiscoveryHost.cs | 120 + .../HappyEyeballs/HttpClientExtension.cs | 119 + .../Jellyfin.Networking.csproj | 17 + .../Manager/NetworkManager.cs | 1177 ++ src/Jellyfin.Networking/Udp/SocketFactory.cs | 38 + ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../net10.0/Jellyfin.Networking.assets.cache | Bin 0 -> 16666 bytes ....Networking.csproj.AssemblyReference.cache | Bin 0 -> 4462 bytes ...llyfin.Networking.csproj.nuget.dgspec.json | 4749 +++++ .../Jellyfin.Networking.csproj.nuget.g.props | 26 + ...Jellyfin.Networking.csproj.nuget.g.targets | 9 + .../obj/project.assets.json | 1875 ++ .../obj/project.nuget.cache | 38 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + stylecop.json | 9 + tests/Directory.Build.props | 22 + .../Auth/CustomAuthenticationHandlerTests.cs | 158 + .../DefaultAuthorizationHandlerTests.cs | 140 + .../FirstTimeSetupHandlerTests.cs | 126 + .../IgnoreScheduleHandlerTests.cs | 62 + .../Controllers/DynamicHlsControllerTests.cs | 45 + .../Controllers/ImageControllerTests.cs | 35 + .../Controllers/SystemControllerTests.cs | 36 + .../Controllers/UserControllerTests.cs | 120 + .../Helpers/RequestHelpersTests.cs | 140 + .../Jellyfin.Api.Tests.csproj | 28 + .../Middleware/UrlDecodeQueryFeatureTests.cs | 27 + ...ommaDelimitedCollectionModelBinderTests.cs | 230 + ...PipeDelimitedCollectionModelBinderTests.cs | 230 + .../ModelBinders/TestType.cs | 11 + tests/Jellyfin.Api.Tests/TestHelpers.cs | 85 + ...erageSourceRootsMapping_Jellyfin.Api.Tests | Bin 0 -> 4808 bytes ...erletSourceRootsMapping_Jellyfin.Api.Tests | Bin 0 -> 4808 bytes ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + .../Jellyfin.Api.Tests.AssemblyInfo.cs | 22 + ...ellyfin.Api.Tests.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../net10.0/Jellyfin.Api.Tests.assets.cache | Bin 0 -> 84696 bytes ...n.Api.Tests.csproj.AssemblyReference.cache | Bin 0 -> 23920 bytes ...ellyfin.Api.Tests.csproj.nuget.dgspec.json | 7700 +++++++ .../Jellyfin.Api.Tests.csproj.nuget.g.props | 33 + .../Jellyfin.Api.Tests.csproj.nuget.g.targets | 17 + .../obj/project.assets.json | 5544 +++++ .../obj/project.nuget.cache | 105 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + tests/Jellyfin.Common.Tests/Crc32Tests.cs | 33 + .../Jellyfin.Common.Tests.csproj | 24 + .../Providers/ProviderIdParserTests.cs | 85 + ...geSourceRootsMapping_Jellyfin.Common.Tests | Bin 0 -> 3134 bytes ...etSourceRootsMapping_Jellyfin.Common.Tests | Bin 0 -> 3134 bytes ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + .../Jellyfin.Common.Tests.AssemblyInfo.cs | 22 + ...yfin.Common.Tests.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../Jellyfin.Common.Tests.assets.cache | Bin 0 -> 58219 bytes ...ommon.Tests.csproj.AssemblyReference.cache | Bin 0 -> 17907 bytes ...yfin.Common.Tests.csproj.nuget.dgspec.json | 5282 +++++ ...Jellyfin.Common.Tests.csproj.nuget.g.props | 31 + ...llyfin.Common.Tests.csproj.nuget.g.targets | 14 + .../obj/project.assets.json | 3464 ++++ .../obj/project.nuget.cache | 67 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../BaseItemManagerTests.cs | 80 + .../DirectoryServiceTests.cs | 252 + .../Entities/BaseItemTests.cs | 46 + .../Jellyfin.Controller.Tests.csproj | 23 + ...urceRootsMapping_Jellyfin.Controller.Tests | Bin 0 -> 2824 bytes ...urceRootsMapping_Jellyfin.Controller.Tests | Bin 0 -> 2824 bytes ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + .../Jellyfin.Controller.Tests.AssemblyInfo.cs | 22 + ....Controller.Tests.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../Jellyfin.Controller.Tests.assets.cache | Bin 0 -> 49102 bytes ...oller.Tests.csproj.AssemblyReference.cache | Bin 0 -> 13229 bytes ....Controller.Tests.csproj.nuget.dgspec.json | 4766 +++++ ...yfin.Controller.Tests.csproj.nuget.g.props | 31 + ...in.Controller.Tests.csproj.nuget.g.targets | 14 + .../obj/project.assets.json | 2925 +++ .../obj/project.nuget.cache | 54 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../CopyToExtensionsTests.cs | 49 + .../FileHelperTests.cs | 23 + .../FormattingStreamWriterTests.cs | 23 + .../Jellyfin.Extensions.Tests.csproj | 22 + .../Json/Converters/JsonBoolNumberTests.cs | 45 + .../Json/Converters/JsonBoolStringTests.cs | 37 + .../JsonCommaDelimitedCollectionTests.cs | 208 + .../JsonCommaDelimitedIReadOnlyListTests.cs | 104 + .../JsonDefaultStringEnumConverterTests.cs | 112 + .../Json/Converters/JsonFlagEnumTests.cs | 28 + .../Json/Converters/JsonGuidConverterTests.cs | 68 + .../JsonNullableGuidConverterTests.cs | 80 + .../Converters/JsonStringConverterTests.cs | 38 + .../Converters/JsonVersionConverterTests.cs | 36 + .../Json/Models/GenericBodyArrayModel.cs | 20 + .../GenericBodyIReadOnlyCollectionModel.cs | 19 + .../Models/GenericBodyIReadOnlyListModel.cs | 19 + .../Json/Models/GenericBodyListModel.cs | 22 + .../ShuffleExtensionsTests.cs | 19 + .../StringExtensionsTests.cs | 79 + ...urceRootsMapping_Jellyfin.Extensions.Tests | Bin 0 -> 1588 bytes ...urceRootsMapping_Jellyfin.Extensions.Tests | Bin 0 -> 1588 bytes ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + .../Jellyfin.Extensions.Tests.AssemblyInfo.cs | 22 + ....Extensions.Tests.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../Jellyfin.Extensions.Tests.assets.cache | Bin 0 -> 48855 bytes ...sions.Tests.csproj.AssemblyReference.cache | Bin 0 -> 13026 bytes ....Extensions.Tests.csproj.nuget.dgspec.json | 2744 +++ ...yfin.Extensions.Tests.csproj.nuget.g.props | 31 + ...in.Extensions.Tests.csproj.nuget.g.targets | 13 + .../obj/project.assets.json | 2696 +++ .../obj/project.nuget.cache | 50 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../HdHomerunHostTests.cs | 156 + .../HdHomerunManagerTests.cs | 326 + .../Jellyfin.LiveTv.Tests.csproj | 29 + .../Listings/ListingsManagerTests.cs | 50 + .../Listings/XmlTvListingsProviderTests.cs | 89 + .../RecordingHelperTests.cs | 109 + .../SchedulesDirectDeserializeTests.cs | 240 + .../LiveTv/10.10.10.100/discover.json | 1 + .../Test Data/LiveTv/10.10.10.100/lineup.json | 1 + .../LiveTv/192.168.1.182/discover.json | 1 + .../LiveTv/192.168.1.182/lineup.json | 1 + .../LiveTv/Listings/XmlTv/emptycategory.xml | 6 + .../LiveTv/Listings/XmlTv/notitle.xml | 10 + .../SchedulesDirect/headends_response.json | 1 + .../SchedulesDirect/lineup_response.json | 1 + .../SchedulesDirect/lineups_response.json | 1 + .../metadata_programs_response.json | 1 + .../SchedulesDirect/programs_response.json | 1 + .../SchedulesDirect/schedules_request.json | 1 + .../SchedulesDirect/schedules_response.json | 1 + .../SchedulesDirect/token_live_response.json | 1 + .../token_offline_response.json | 1 + ...geSourceRootsMapping_Jellyfin.LiveTv.Tests | Bin 0 -> 3114 bytes ...etSourceRootsMapping_Jellyfin.LiveTv.Tests | Bin 0 -> 3114 bytes ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + .../Jellyfin.LiveTv.Tests.AssemblyInfo.cs | 22 + ...yfin.LiveTv.Tests.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../Jellyfin.LiveTv.Tests.assets.cache | Bin 0 -> 52726 bytes ...iveTv.Tests.csproj.AssemblyReference.cache | Bin 0 -> 15591 bytes ...yfin.LiveTv.Tests.csproj.nuget.dgspec.json | 5262 +++++ ...Jellyfin.LiveTv.Tests.csproj.nuget.g.props | 31 + ...llyfin.LiveTv.Tests.csproj.nuget.g.targets | 14 + .../obj/project.assets.json | 3252 +++ .../obj/project.nuget.cache | 63 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../Jellyfin.MediaEncoding.Hls.Tests.csproj | 21 + .../DynamicHlsPlaylistGeneratorTests.cs | 101 + ...tsMapping_Jellyfin.MediaEncoding.Hls.Tests | Bin 0 -> 3158 bytes ...tsMapping_Jellyfin.MediaEncoding.Hls.Tests | Bin 0 -> 3158 bytes ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ...in.MediaEncoding.Hls.Tests.AssemblyInfo.cs | 22 + ...ncoding.Hls.Tests.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + ...lyfin.MediaEncoding.Hls.Tests.assets.cache | Bin 0 -> 47140 bytes ...g.Hls.Tests.csproj.AssemblyReference.cache | Bin 0 -> 12443 bytes ...ncoding.Hls.Tests.csproj.nuget.dgspec.json | 5240 +++++ ...diaEncoding.Hls.Tests.csproj.nuget.g.props | 31 + ...aEncoding.Hls.Tests.csproj.nuget.g.targets | 14 + .../obj/project.assets.json | 2814 +++ .../obj/project.nuget.cache | 51 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../FfProbe/FfProbeKeyframeExtractorTests.cs | 28 + .../FfProbe/Test Data/keyframes.txt | 16937 ++++++++++++++++ .../FfProbe/Test Data/keyframes_result.json | 1 + .../Test Data/keyframes_streamduration.txt | 2398 +++ .../keyframes_streamduration_result.json | 1 + ...lyfin.MediaEncoding.Keyframes.Tests.csproj | 35 + ...ing_Jellyfin.MediaEncoding.Keyframes.Tests | Bin 0 -> 674 bytes ...ing_Jellyfin.MediaEncoding.Keyframes.Tests | Bin 0 -> 674 bytes ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ...iaEncoding.Keyframes.Tests.AssemblyInfo.cs | 22 + ...g.Keyframes.Tests.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + ...MediaEncoding.Keyframes.Tests.assets.cache | Bin 0 -> 33234 bytes ...rames.Tests.csproj.AssemblyReference.cache | Bin 0 -> 8212 bytes ...g.Keyframes.Tests.csproj.nuget.dgspec.json | 1154 ++ ...oding.Keyframes.Tests.csproj.nuget.g.props | 30 + ...ing.Keyframes.Tests.csproj.nuget.g.targets | 11 + .../obj/project.assets.json | 1645 ++ .../obj/project.nuget.cache | 29 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../EncoderValidatorTests.cs | 48 + .../EncoderValidatorTestsData.cs | 101 + .../Jellyfin.MediaEncoding.Tests.csproj | 32 + .../Probing/ProbeExternalSourcesTests.cs | 50 + .../Probing/ProbeResultNormalizerTests.cs | 407 + .../Subtitles/AssParserTests.cs | 27 + .../Subtitles/SrtParserTests.cs | 54 + .../Subtitles/SsaParserTests.cs | 87 + .../Subtitles/SubtitleEncoderTests.cs | 107 + .../Test Data/Probing/music_metadata.json | 144 + .../Probing/music_video_metadata.json | 111 + .../Probing/music_year_only_metadata.json | 147 + .../Test Data/Probing/video_interlaced.json | 81 + .../Test Data/Probing/video_metadata.json | 177 + .../Test Data/Probing/video_mp4_metadata.json | 260 + .../video_progressive_no_field_order.json | 133 + .../video_progressive_no_field_order2.json | 72 + .../Probing/video_single_frame_mjpeg.json | 209 + .../Test Data/Probing/video_ts.json | 105 + .../video_web_like_mkv_with_subtitle.json | 137 + .../Test Data/Probing/video_webm.json | 106 + .../Test Data/example.ass | 22 + .../Test Data/example.srt | 8 + .../Test Data/example.ssa | 20 + .../Test Data/example2.srt | 11 + ...eRootsMapping_Jellyfin.MediaEncoding.Tests | Bin 0 -> 3150 bytes ...eRootsMapping_Jellyfin.MediaEncoding.Tests | Bin 0 -> 3150 bytes ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ...llyfin.MediaEncoding.Tests.AssemblyInfo.cs | 22 + ...diaEncoding.Tests.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../Jellyfin.MediaEncoding.Tests.assets.cache | Bin 0 -> 58124 bytes ...oding.Tests.csproj.AssemblyReference.cache | Bin 0 -> 17650 bytes ...diaEncoding.Tests.csproj.nuget.dgspec.json | 5277 +++++ ...n.MediaEncoding.Tests.csproj.nuget.g.props | 31 + ...MediaEncoding.Tests.csproj.nuget.g.targets | 14 + .../obj/project.assets.json | 3635 ++++ .../obj/project.nuget.cache | 71 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../Cryptography/PasswordHashTests.cs | 169 + .../Dlna/ContainerHelperTests.cs | 83 + .../Dlna/LegacyStreamInfo.cs | 213 + .../Dlna/StreamBuilderTests.cs | 621 + .../Dlna/StreamInfoTests.cs | 243 + .../Drawing/ImageFormatExtensionsTests.cs | 46 + .../Entities/MediaStreamTests.cs | 250 + .../Entities/ProviderIdsExtensionsTests.cs | 206 + .../Extensions/StringHelperTests.cs | 33 + .../Jellyfin.Model.Tests.csproj | 25 + .../Net/MimeTypesTests.cs | 171 + .../Test Data/DeviceProfile-AndroidPixel.json | 329 + .../DeviceProfile-AndroidTVExoPlayer.json | 214 + .../Test Data/DeviceProfile-Chrome-NoHLS.json | 419 + .../Test Data/DeviceProfile-Chrome.json | 358 + .../Test Data/DeviceProfile-DirectMedia.json | 90 + .../Test Data/DeviceProfile-Firefox.json | 369 + .../DeviceProfile-JellyfinMediaPlayer.json | 134 + .../Test Data/DeviceProfile-LowBandwidth.json | 134 + .../Test Data/DeviceProfile-Null.json | 9 + .../Test Data/DeviceProfile-RokuSSPlus.json | 205 + .../DeviceProfile-RokuSSPlusNext.json | 205 + .../Test Data/DeviceProfile-SafariNext.json | 352 + .../DeviceProfile-Tizen3-stereo.json | 555 + .../DeviceProfile-Tizen4-4K-5.1.json | 528 + .../DeviceProfile-TranscodeMedia.json | 135 + .../Test Data/DeviceProfile-WebOS-23.json | 353 + .../Test Data/DeviceProfile-Yatse.json | 186 + .../Test Data/DeviceProfile-Yatse2.json | 186 + ...MediaSourceInfo-mkv-av1-aac-srt-2600k.json | 73 + ...iaSourceInfo-mkv-av1-vorbis-srt-2600k.json | 72 + ...diaSourceInfo-mkv-dvhe.05-eac3-28000k.json | 95 + ...diaSourceInfo-mkv-dvhe.08-eac3-15200k.json | 97 + ...ediaSourceInfo-mkv-h264-ac3-srt-2600k.json | 71 + ...fo-mkv-h264-hi10p-aac-5000k-brokenfps.json | 82 + ...MediaSourceInfo-mkv-vp9-aac-srt-2600k.json | 73 + ...MediaSourceInfo-mkv-vp9-ac3-srt-2600k.json | 72 + ...iaSourceInfo-mkv-vp9-vorbis-srt-2600k.json | 73 + ...iaSourceInfo-mkv-vp9-vorbis-vtt-2600k.json | 72 + ...diaSourceInfo-mp4-dvh1.05-eac3-15200k.json | 94 + ...diaSourceInfo-mp4-dvhe.08-eac3-15200k.json | 97 + ...ediaSourceInfo-mp4-h264-aac-srt-2600k.json | 72 + ...ediaSourceInfo-mp4-h264-aac-vtt-2600k.json | 72 + ...ceInfo-mp4-h264-ac3-aac-aac-srt-2600k.json | 102 + ...ceInfo-mp4-h264-ac3-aac-mp3-srt-2600k.json | 100 + ...SourceInfo-mp4-h264-ac3-aac-srt-2600k.json | 87 + ...rceInfo-mp4-h264-ac3-aacDef-srt-2600k.json | 87 + ...rceInfo-mp4-h264-ac3-aacExt-srt-2600k.json | 89 + ...ediaSourceInfo-mp4-h264-ac3-srt-2600k.json | 71 + ...ediaSourceInfo-mp4-h264-dts-srt-2600k.json | 70 + ...iaSourceInfo-mp4-h264-hi10p-aac-5000k.json | 86 + ...diaSourceInfo-mp4-hevc-aac-srt-15200k.json | 75 + ...ourceInfo-mp4-hevc-ac3-aac-srt-15200k.json | 89 + ...ceInfo-mp4-hevc-ac3-aacDef-srt-15200k.json | 89 + ...ceInfo-mp4-hevc-ac3-aacExt-srt-15200k.json | 91 + ...diaSourceInfo-mp4-hevc-ac3-srt-15200k.json | 74 + ...SourceInfo-mp4-hevc-truehd-srt-15200k.json | 74 + .../Test Data/MediaSourceInfo-no-streams.json | 17 + .../MediaSourceInfo-numstreams-32.json | 565 + .../MediaSourceInfo-numstreams-33.json | 582 + .../Test Data/MediaSourceInfo-raw.json | 102 + ...ageSourceRootsMapping_Jellyfin.Model.Tests | Bin 0 -> 1588 bytes ...letSourceRootsMapping_Jellyfin.Model.Tests | Bin 0 -> 1588 bytes ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + .../Jellyfin.Model.Tests.AssemblyInfo.cs | 22 + ...lyfin.Model.Tests.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../net10.0/Jellyfin.Model.Tests.assets.cache | Bin 0 -> 50879 bytes ...Model.Tests.csproj.AssemblyReference.cache | Bin 0 -> 13812 bytes ...lyfin.Model.Tests.csproj.nuget.dgspec.json | 2744 +++ .../Jellyfin.Model.Tests.csproj.nuget.g.props | 31 + ...ellyfin.Model.Tests.csproj.nuget.g.targets | 13 + .../obj/project.assets.json | 2829 +++ .../obj/project.nuget.cache | 53 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../AudioBook/AudioBookFileInfoTests.cs | 30 + .../AudioBook/AudioBookListResolverTests.cs | 272 + .../AudioBook/AudioBookResolverTests.cs | 65 + .../Common/NamingOptionsTest.cs | 33 + .../ExternalFiles/ExternalPathParserTests.cs | 121 + .../Jellyfin.Naming.Tests.csproj | 23 + .../Music/MultiDiscAlbumTests.cs | 50 + .../TV/AbsoluteEpisodeNumberTests.cs | 26 + .../TV/DailyEpisodeTests.cs | 32 + .../TV/EpisodeNumberTests.cs | 96 + .../TV/EpisodeNumberWithoutSeasonTests.cs | 34 + .../TV/EpisodePathParserTest.cs | 104 + .../TV/MultiEpisodeTests.cs | 79 + .../TV/SeasonNumberTests.cs | 64 + .../TV/SeasonPathParserTests.cs | 86 + .../TV/SeriesPathParserTest.cs | 29 + .../TV/SeriesResolverTests.cs | 30 + .../TV/SimpleEpisodeTests.cs | 49 + .../TV/TvParserHelpersTest.cs | 31 + .../Video/CleanDateTimeTests.cs | 66 + .../Video/CleanStringTests.cs | 53 + .../Jellyfin.Naming.Tests/Video/ExtraTests.cs | 168 + .../Video/Format3DTests.cs | 74 + .../Video/MultiVersionTests.cs | 439 + .../Jellyfin.Naming.Tests/Video/StackTests.cs | 403 + .../Jellyfin.Naming.Tests/Video/StubTests.cs | 53 + .../Video/VideoListResolverTests.cs | 417 + .../Video/VideoResolverTests.cs | 194 + ...geSourceRootsMapping_Jellyfin.Naming.Tests | Bin 0 -> 2152 bytes ...etSourceRootsMapping_Jellyfin.Naming.Tests | Bin 0 -> 2152 bytes ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + .../Jellyfin.Naming.Tests.AssemblyInfo.cs | 22 + ...yfin.Naming.Tests.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../Jellyfin.Naming.Tests.assets.cache | Bin 0 -> 46515 bytes ...aming.Tests.csproj.AssemblyReference.cache | Bin 0 -> 12572 bytes ...yfin.Naming.Tests.csproj.nuget.dgspec.json | 3815 ++++ ...Jellyfin.Naming.Tests.csproj.nuget.g.props | 31 + ...llyfin.Naming.Tests.csproj.nuget.g.targets | 13 + .../obj/project.assets.json | 2712 +++ .../obj/project.nuget.cache | 50 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../NetworkConfigurationTests.cs | 27 + .../Jellyfin.Networking.Tests.csproj | 24 + .../NetworkExtensionsTests.cs | 53 + .../NetworkManagerTests.cs | 68 + .../NetworkParseTests.cs | 398 + ...urceRootsMapping_Jellyfin.Networking.Tests | Bin 0 -> 3130 bytes ...urceRootsMapping_Jellyfin.Networking.Tests | Bin 0 -> 3130 bytes ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + .../Jellyfin.Networking.Tests.AssemblyInfo.cs | 22 + ....Networking.Tests.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../Jellyfin.Networking.Tests.assets.cache | Bin 0 -> 53742 bytes ...rking.Tests.csproj.AssemblyReference.cache | Bin 0 -> 14469 bytes ....Networking.Tests.csproj.nuget.dgspec.json | 5239 +++++ ...yfin.Networking.Tests.csproj.nuget.g.props | 31 + ...in.Networking.Tests.csproj.nuget.g.targets | 14 + .../obj/project.assets.json | 3104 +++ .../obj/project.nuget.cache | 57 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../Jellyfin.Providers.Tests.csproj | 30 + .../Lyrics/LrcLyricParserTests.cs | 47 + .../Manager/ItemImageProviderTests.cs | 658 + .../Manager/MetadataServiceTests.cs | 387 + .../Manager/ProviderManagerTests.cs | 622 + .../MediaInfo/AudioResolverTests.cs | 90 + .../MediaInfo/EmbeddedImageProviderTests.cs | 158 + .../MediaInfo/FFProbeVideoInfoTests.cs | 61 + .../MediaInfo/MediaInfoResolverTests.cs | 431 + .../MediaInfo/SubtitleResolverTests.cs | 90 + .../MediaInfo/VideoImageProviderTests.cs | 126 + .../Omdb/JsonOmdbConverterTests.cs | 86 + .../Test Data/Images/blank0.jpg | 0 .../Test Data/Images/blank1.jpg | 0 .../Lyrics/Fleetwood Mac - Rumors.elrc | 31 + .../Tmdb/TmdbUtilsTests.cs | 38 + ...ourceRootsMapping_Jellyfin.Providers.Tests | Bin 0 -> 3134 bytes ...ourceRootsMapping_Jellyfin.Providers.Tests | Bin 0 -> 3134 bytes ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + .../Jellyfin.Providers.Tests.AssemblyInfo.cs | 22 + ...n.Providers.Tests.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../Jellyfin.Providers.Tests.assets.cache | Bin 0 -> 58256 bytes ...iders.Tests.csproj.AssemblyReference.cache | Bin 0 -> 19155 bytes ...n.Providers.Tests.csproj.nuget.dgspec.json | 5296 +++++ ...lyfin.Providers.Tests.csproj.nuget.g.props | 31 + ...fin.Providers.Tests.csproj.nuget.g.targets | 14 + .../obj/project.assets.json | 3647 ++++ .../obj/project.nuget.cache | 73 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../Cryptography/CryptographyProviderTests.cs | 102 + .../Data/SearchPunctuationTests.cs | 109 + .../Data/SqliteItemRepositoryTests.cs | 187 + .../EfMigrations/EfMigrationTests.cs | 17 + .../HttpServer/WebSocketConnectionTests.cs | 69 + .../IO/ManagedFileSystemTests.cs | 123 + .../Item/BaseItemRepositoryTests.cs | 72 + .../Item/OrderMapperTests.cs | 35 + ...llyfin.Server.Implementations.Tests.csproj | 35 + .../Library/AudioResolverTests.cs | 77 + .../Library/CoreResolutionIgnoreRuleTest.cs | 129 + .../Library/DotIgnoreIgnoreRuleTest.cs | 81 + .../Library/EpisodeResolverTest.cs | 73 + .../Library/IgnorePatternsTests.cs | 40 + .../Library/LibraryManager/FindExtrasTests.cs | 334 + .../Library/MediaSourceManagerTests.cs | 32 + .../Library/MediaStreamSelectorTests.cs | 118 + .../Library/MovieResolverTests.cs | 35 + .../Library/PathExtensionsTests.cs | 137 + .../Localization/LocalizationManagerTests.cs | 263 + .../Playlists/PlaylistManagerTests.cs | 40 + .../Plugins/PluginManagerTests.cs | 339 + .../QuickConnect/QuickConnectManagerTests.cs | 139 + .../SessionManager/SessionManagerTests.cs | 111 + .../Sorting/AiredEpisodeOrderComparerTests.cs | 163 + .../Sorting/IndexNumberComparerTests.cs | 49 + .../Sorting/ParentIndexNumberComparerTests.cs | 49 + .../Sorting/PremiereDateComparerTests.cs | 76 + .../Test Data/HttpServer/ForceKeepAlive.json | 1 + .../Test Data/HttpServer/Partial.json | 1 + .../Test Data/HttpServer/ValidPartial.json | 1 + .../Test Data/Updates/empty.zip | Bin 0 -> 162 bytes .../Test Data/Updates/manifest.json | 684 + .../TypedBaseItem/BaseItemKindTests.cs | 63 + .../Updates/InstallationManagerTests.cs | 110 + .../Users/UserManagerTests.cs | 35 + ...ping_Jellyfin.Server.Implementations.Tests | Bin 0 -> 7908 bytes ...ping_Jellyfin.Server.Implementations.Tests | Bin 0 -> 7908 bytes ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ...rver.Implementations.Tests.AssemblyInfo.cs | 22 + ...ementations.Tests.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + ....Server.Implementations.Tests.assets.cache | Bin 0 -> 116275 bytes ...tions.Tests.csproj.AssemblyReference.cache | Bin 0 -> 44395 bytes ...ementations.Tests.csproj.nuget.dgspec.json | 12834 ++++++++++++ ...Implementations.Tests.csproj.nuget.g.props | 33 + ...plementations.Tests.csproj.nuget.g.targets | 17 + .../obj/project.assets.json | 7986 ++++++++ .../obj/project.nuget.cache | 163 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../AuthHelper.cs | 85 + .../Controllers/ActivityLogControllerTests.cs | 30 + .../Controllers/BaseJellyfinTestController.cs | 14 + .../Controllers/BrandingControllerTests.cs | 54 + .../Controllers/DashboardControllerTests.cs | 89 + .../Controllers/EncoderController.cs | 53 + .../Controllers/ItemsControllerTests.cs | 63 + .../Controllers/LibraryControllerTests.cs | 63 + .../LibraryStructureControllerTests.cs | 127 + .../Controllers/LiveTvControllerTests.cs | 96 + .../Controllers/MediaInfoControllerTests.cs | 61 + .../MediaStructureControllerTests.cs | 117 + .../Controllers/MusicGenreControllerTests.cs | 26 + .../Controllers/PersonsControllerTests.cs | 26 + .../Controllers/PlaystateControllerTests.cs | 61 + .../Controllers/PluginsControllerTests.cs | 45 + .../Controllers/StartupControllerTests.cs | 116 + .../Controllers/UserControllerTests.cs | 168 + .../Controllers/UserLibraryControllerTests.cs | 124 + .../Controllers/VideosControllerTests.cs | 27 + .../EncodedQueryStringTest.cs | 49 + .../Jellyfin.Server.Integration.Tests.csproj | 37 + .../JellyfinApplicationFactory.cs | 194 + .../RobotsRedirectionMiddlewareTests.cs | 32 + .../OpenApiSpecTests.cs | 41 + .../Test Data/dummy.m3u8 | 1 + .../TestAppHost.cs | 46 + .../TestPage.html | 9 + .../TestPlugin.cs | 43 + .../TestPluginWithoutPages.cs | 27 + .../WebSocketTests.cs | 33 + ...sMapping_Jellyfin.Server.Integration.Tests | Bin 0 -> 7542 bytes ...sMapping_Jellyfin.Server.Integration.Tests | Bin 0 -> 7542 bytes ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ...n.Server.Integration.Tests.AssemblyInfo.cs | 22 + ...Integration.Tests.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + ...yfin.Server.Integration.Tests.assets.cache | Bin 0 -> 116156 bytes ...ation.Tests.csproj.AssemblyReference.cache | Bin 0 -> 43946 bytes ...Integration.Tests.csproj.nuget.dgspec.json | 12325 +++++++++++ ...ver.Integration.Tests.csproj.nuget.g.props | 33 + ...r.Integration.Tests.csproj.nuget.g.targets | 17 + .../obj/project.assets.json | 7889 +++++++ .../obj/project.nuget.cache | 161 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../xunit.runner.json | 4 + .../Jellyfin.Server.Tests.csproj | 22 + .../ParseNetworkTests.cs | 127 + ...geSourceRootsMapping_Jellyfin.Server.Tests | Bin 0 -> 7542 bytes ...etSourceRootsMapping_Jellyfin.Server.Tests | Bin 0 -> 7542 bytes ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + .../Jellyfin.Server.Tests.AssemblyInfo.cs | 22 + ...yfin.Server.Tests.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../Jellyfin.Server.Tests.assets.cache | Bin 0 -> 115580 bytes ...erver.Tests.csproj.AssemblyReference.cache | Bin 0 -> 43702 bytes ...yfin.Server.Tests.csproj.nuget.dgspec.json | 12320 +++++++++++ ...Jellyfin.Server.Tests.csproj.nuget.g.props | 33 + ...llyfin.Server.Tests.csproj.nuget.g.targets | 17 + .../obj/project.assets.json | 7850 +++++++ .../obj/project.nuget.cache | 160 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + .../Jellyfin.XbmcMetadata.Tests.csproj | 25 + .../Location/MovieNfoLocationTests.cs | 68 + .../Parsers/EpisodeNfoProviderTests.cs | 183 + .../Parsers/MovieNfoParserTests.cs | 298 + .../Parsers/MusicAlbumNfoProviderTests.cs | 86 + .../Parsers/MusicArtistNfoParserTests.cs | 86 + .../Parsers/MusicVideoNfoParserTests.cs | 80 + .../Parsers/SeasonNfoProviderTests.cs | 93 + .../Parsers/SeriesNfoParserTests.cs | 117 + .../Test Data/American Gods.nfo | 187 + .../Test Data/Dancing Queen.nfo | 50 + .../Test Data/Fanart.nfo | 33 + .../Test Data/Imdb.nfo | 1 + .../Test Data/Justice League.nfo | 248 + .../Test Data/Lilo & Stitch.nfo | 8 + .../Test Data/Radarr.nfo | 2 + .../Test Data/Rising.nfo | 44 + .../Test Data/Season 01.nfo | 86 + .../Test Data/Sonarr-Thumb.nfo | 34 + .../Stargate Atlantis S01E01-E04.nfo | 89 + .../Test Data/The Best of 1980-1990.nfo | 29 + .../Test Data/The Bone Orchard.nfo | 116 + .../Test Data/Tmdb.nfo | 1 + .../Test Data/Tvdb.nfo | 1 + .../Test Data/U2.nfo | 70 + ...ceRootsMapping_Jellyfin.XbmcMetadata.Tests | Bin 0 -> 3456 bytes ...ceRootsMapping_Jellyfin.XbmcMetadata.Tests | Bin 0 -> 3456 bytes ...oreApp,Version=v10.0.AssemblyAttributes.cs | 4 + ...ellyfin.XbmcMetadata.Tests.AssemblyInfo.cs | 22 + ...bmcMetadata.Tests.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 27 + .../Jellyfin.XbmcMetadata.Tests.assets.cache | Bin 0 -> 55696 bytes ...adata.Tests.csproj.AssemblyReference.cache | Bin 0 -> 17453 bytes ...bmcMetadata.Tests.csproj.nuget.dgspec.json | 5750 ++++++ ...in.XbmcMetadata.Tests.csproj.nuget.g.props | 31 + ....XbmcMetadata.Tests.csproj.nuget.g.targets | 14 + .../obj/project.assets.json | 3464 ++++ .../obj/project.nuget.cache | 67 + .../obj/project.packagespec.json | 1 + .../obj/rider.project.model.nuget.info | 1 + .../obj/rider.project.restore.info | 1 + tests/coverletArgs.runsettings | 18 + 2860 files changed, 650825 insertions(+) create mode 100644 .idea/.idea.Jellyfin/.idea/encodings.xml create mode 100644 BannedSymbols.txt create mode 100644 CONTRIBUTORS.md create mode 100644 Directory.Build.props create mode 100644 Directory.Packages.props create mode 100644 Emby.Naming/Audio/AlbumParser.cs create mode 100644 Emby.Naming/Audio/AudioFileParser.cs create mode 100644 Emby.Naming/AudioBook/AudioBookFileInfo.cs create mode 100644 Emby.Naming/AudioBook/AudioBookFilePathParser.cs create mode 100644 Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs create mode 100644 Emby.Naming/AudioBook/AudioBookInfo.cs create mode 100644 Emby.Naming/AudioBook/AudioBookListResolver.cs create mode 100644 Emby.Naming/AudioBook/AudioBookNameParser.cs create mode 100644 Emby.Naming/AudioBook/AudioBookNameParserResult.cs create mode 100644 Emby.Naming/AudioBook/AudioBookResolver.cs create mode 100644 Emby.Naming/Book/BookFileNameParser.cs create mode 100644 Emby.Naming/Book/BookFileNameParserResult.cs create mode 100644 Emby.Naming/Common/EpisodeExpression.cs create mode 100644 Emby.Naming/Common/MediaType.cs create mode 100644 Emby.Naming/Common/NamingOptions.cs create mode 100644 Emby.Naming/Emby.Naming.csproj create mode 100644 Emby.Naming/ExternalFiles/ExternalPathParser.cs create mode 100644 Emby.Naming/ExternalFiles/ExternalPathParserResult.cs create mode 100644 Emby.Naming/Properties/AssemblyInfo.cs create mode 100644 Emby.Naming/TV/EpisodeInfo.cs create mode 100644 Emby.Naming/TV/EpisodePathParser.cs create mode 100644 Emby.Naming/TV/EpisodePathParserResult.cs create mode 100644 Emby.Naming/TV/EpisodeResolver.cs create mode 100644 Emby.Naming/TV/SeasonPathParser.cs create mode 100644 Emby.Naming/TV/SeasonPathParserResult.cs create mode 100644 Emby.Naming/TV/SeriesInfo.cs create mode 100644 Emby.Naming/TV/SeriesPathParser.cs create mode 100644 Emby.Naming/TV/SeriesPathParserResult.cs create mode 100644 Emby.Naming/TV/SeriesResolver.cs create mode 100644 Emby.Naming/TV/TvParserHelpers.cs create mode 100644 Emby.Naming/Video/CleanDateTimeParser.cs create mode 100644 Emby.Naming/Video/CleanDateTimeResult.cs create mode 100644 Emby.Naming/Video/CleanStringParser.cs create mode 100644 Emby.Naming/Video/ExtraResult.cs create mode 100644 Emby.Naming/Video/ExtraRule.cs create mode 100644 Emby.Naming/Video/ExtraRuleResolver.cs create mode 100644 Emby.Naming/Video/ExtraRuleType.cs create mode 100644 Emby.Naming/Video/FileStack.cs create mode 100644 Emby.Naming/Video/FileStackRule.cs create mode 100644 Emby.Naming/Video/Format3DParser.cs create mode 100644 Emby.Naming/Video/Format3DResult.cs create mode 100644 Emby.Naming/Video/Format3DRule.cs create mode 100644 Emby.Naming/Video/StackResolver.cs create mode 100644 Emby.Naming/Video/StubResolver.cs create mode 100644 Emby.Naming/Video/StubTypeRule.cs create mode 100644 Emby.Naming/Video/VideoFileInfo.cs create mode 100644 Emby.Naming/Video/VideoInfo.cs create mode 100644 Emby.Naming/Video/VideoListResolver.cs create mode 100644 Emby.Naming/Video/VideoResolver.cs create mode 100644 Emby.Naming/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 Emby.Naming/obj/Debug/net10.0/Emby.Naming.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 Emby.Naming/obj/Debug/net10.0/Emby.Naming.assets.cache create mode 100644 Emby.Naming/obj/Debug/net10.0/Emby.Naming.csproj.AssemblyReference.cache create mode 100644 Emby.Naming/obj/Emby.Naming.csproj.nuget.dgspec.json create mode 100644 Emby.Naming/obj/Emby.Naming.csproj.nuget.g.props create mode 100644 Emby.Naming/obj/Emby.Naming.csproj.nuget.g.targets create mode 100644 Emby.Naming/obj/project.assets.json create mode 100644 Emby.Naming/obj/project.nuget.cache create mode 100644 Emby.Naming/obj/project.packagespec.json create mode 100644 Emby.Naming/obj/rider.project.model.nuget.info create mode 100644 Emby.Naming/obj/rider.project.restore.info create mode 100644 Emby.Photos/Emby.Photos.csproj create mode 100644 Emby.Photos/PhotoProvider.cs create mode 100644 Emby.Photos/Properties/AssemblyInfo.cs create mode 100644 Emby.Photos/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 Emby.Photos/obj/Debug/net10.0/Emby.Photos.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 Emby.Photos/obj/Debug/net10.0/Emby.Photos.assets.cache create mode 100644 Emby.Photos/obj/Debug/net10.0/Emby.Photos.csproj.AssemblyReference.cache create mode 100644 Emby.Photos/obj/Emby.Photos.csproj.nuget.dgspec.json create mode 100644 Emby.Photos/obj/Emby.Photos.csproj.nuget.g.props create mode 100644 Emby.Photos/obj/Emby.Photos.csproj.nuget.g.targets create mode 100644 Emby.Photos/obj/project.assets.json create mode 100644 Emby.Photos/obj/project.nuget.cache create mode 100644 Emby.Photos/obj/project.packagespec.json create mode 100644 Emby.Photos/obj/rider.project.model.nuget.info create mode 100644 Emby.Photos/obj/rider.project.restore.info create mode 100644 Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs create mode 100644 Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs create mode 100644 Emby.Server.Implementations/AppBase/ConfigurationHelper.cs create mode 100644 Emby.Server.Implementations/ApplicationHost.cs create mode 100644 Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs create mode 100644 Emby.Server.Implementations/Chapters/ChapterManager.cs create mode 100644 Emby.Server.Implementations/Collections/CollectionImageProvider.cs create mode 100644 Emby.Server.Implementations/Collections/CollectionManager.cs create mode 100644 Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs create mode 100644 Emby.Server.Implementations/ConfigurationOptions.cs create mode 100644 Emby.Server.Implementations/Cryptography/CryptographyProvider.cs create mode 100644 Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs create mode 100644 Emby.Server.Implementations/Data/ItemTypeLookup.cs create mode 100644 Emby.Server.Implementations/Data/SqliteExtensions.cs create mode 100644 Emby.Server.Implementations/Data/TypeMapper.cs create mode 100644 Emby.Server.Implementations/Devices/DeviceId.cs create mode 100644 Emby.Server.Implementations/Dto/DtoService.cs create mode 100644 Emby.Server.Implementations/Emby.Server.Implementations.csproj create mode 100644 Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs create mode 100644 Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs create mode 100644 Emby.Server.Implementations/HttpServer/Security/AuthService.cs create mode 100644 Emby.Server.Implementations/HttpServer/WebSocketConnection.cs create mode 100644 Emby.Server.Implementations/HttpServer/WebSocketManager.cs create mode 100644 Emby.Server.Implementations/IO/FileRefresher.cs create mode 100644 Emby.Server.Implementations/IO/LibraryMonitor.cs create mode 100644 Emby.Server.Implementations/IO/ManagedFileSystem.cs create mode 100644 Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs create mode 100644 Emby.Server.Implementations/IStartupOptions.cs create mode 100644 Emby.Server.Implementations/Images/ArtistImageProvider.cs create mode 100644 Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs create mode 100644 Emby.Server.Implementations/Images/BaseFolderImageProvider.cs create mode 100644 Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs create mode 100644 Emby.Server.Implementations/Images/DynamicImageProvider.cs create mode 100644 Emby.Server.Implementations/Images/FolderImageProvider.cs create mode 100644 Emby.Server.Implementations/Images/GenreImageProvider.cs create mode 100644 Emby.Server.Implementations/Images/MusicAlbumImageProvider.cs create mode 100644 Emby.Server.Implementations/Images/MusicGenreImageProvider.cs create mode 100644 Emby.Server.Implementations/Images/PhotoAlbumImageProvider.cs create mode 100644 Emby.Server.Implementations/Images/PlaylistImageProvider.cs create mode 100644 Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs create mode 100644 Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs create mode 100644 Emby.Server.Implementations/Library/ExternalDataManager.cs create mode 100644 Emby.Server.Implementations/Library/IgnorePatterns.cs create mode 100644 Emby.Server.Implementations/Library/KeyframeManager.cs create mode 100644 Emby.Server.Implementations/Library/LibraryManager.cs create mode 100644 Emby.Server.Implementations/Library/LiveStreamHelper.cs create mode 100644 Emby.Server.Implementations/Library/MediaSourceManager.cs create mode 100644 Emby.Server.Implementations/Library/MediaStreamSelector.cs create mode 100644 Emby.Server.Implementations/Library/MusicManager.cs create mode 100644 Emby.Server.Implementations/Library/PathExtensions.cs create mode 100644 Emby.Server.Implementations/Library/PathManager.cs create mode 100644 Emby.Server.Implementations/Library/ResolverHelper.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs create mode 100644 Emby.Server.Implementations/Library/SearchEngine.cs create mode 100644 Emby.Server.Implementations/Library/SplashscreenPostScanTask.cs create mode 100644 Emby.Server.Implementations/Library/UserDataManager.cs create mode 100644 Emby.Server.Implementations/Library/UserViewManager.cs create mode 100644 Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs create mode 100644 Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs create mode 100644 Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs create mode 100644 Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs create mode 100644 Emby.Server.Implementations/Library/Validators/GenresValidator.cs create mode 100644 Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs create mode 100644 Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs create mode 100644 Emby.Server.Implementations/Library/Validators/PeopleValidator.cs create mode 100644 Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs create mode 100644 Emby.Server.Implementations/Library/Validators/StudiosValidator.cs create mode 100644 Emby.Server.Implementations/Localization/Core/ab.json create mode 100644 Emby.Server.Implementations/Localization/Core/af.json create mode 100644 Emby.Server.Implementations/Localization/Core/ar.json create mode 100644 Emby.Server.Implementations/Localization/Core/as.json create mode 100644 Emby.Server.Implementations/Localization/Core/be.json create mode 100644 Emby.Server.Implementations/Localization/Core/bg-BG.json create mode 100644 Emby.Server.Implementations/Localization/Core/bn.json create mode 100644 Emby.Server.Implementations/Localization/Core/ca.json create mode 100644 Emby.Server.Implementations/Localization/Core/chr.json create mode 100644 Emby.Server.Implementations/Localization/Core/cs.json create mode 100644 Emby.Server.Implementations/Localization/Core/cy.json create mode 100644 Emby.Server.Implementations/Localization/Core/da.json create mode 100644 Emby.Server.Implementations/Localization/Core/de.json create mode 100644 Emby.Server.Implementations/Localization/Core/el.json create mode 100644 Emby.Server.Implementations/Localization/Core/en-GB.json create mode 100644 Emby.Server.Implementations/Localization/Core/en-US.json create mode 100644 Emby.Server.Implementations/Localization/Core/enm.json create mode 100644 Emby.Server.Implementations/Localization/Core/eo.json create mode 100644 Emby.Server.Implementations/Localization/Core/es-AR.json create mode 100644 Emby.Server.Implementations/Localization/Core/es-MX.json create mode 100644 Emby.Server.Implementations/Localization/Core/es.json create mode 100644 Emby.Server.Implementations/Localization/Core/es_419.json create mode 100644 Emby.Server.Implementations/Localization/Core/es_DO.json create mode 100644 Emby.Server.Implementations/Localization/Core/et.json create mode 100644 Emby.Server.Implementations/Localization/Core/eu.json create mode 100644 Emby.Server.Implementations/Localization/Core/fa.json create mode 100644 Emby.Server.Implementations/Localization/Core/fi.json create mode 100644 Emby.Server.Implementations/Localization/Core/fil.json create mode 100644 Emby.Server.Implementations/Localization/Core/fo.json create mode 100644 Emby.Server.Implementations/Localization/Core/fr-CA.json create mode 100644 Emby.Server.Implementations/Localization/Core/fr.json create mode 100644 Emby.Server.Implementations/Localization/Core/ga.json create mode 100644 Emby.Server.Implementations/Localization/Core/gl.json create mode 100644 Emby.Server.Implementations/Localization/Core/gsw.json create mode 100644 Emby.Server.Implementations/Localization/Core/he.json create mode 100644 Emby.Server.Implementations/Localization/Core/he_IL.json create mode 100644 Emby.Server.Implementations/Localization/Core/hi.json create mode 100644 Emby.Server.Implementations/Localization/Core/hr.json create mode 100644 Emby.Server.Implementations/Localization/Core/ht.json create mode 100644 Emby.Server.Implementations/Localization/Core/hu.json create mode 100644 Emby.Server.Implementations/Localization/Core/hy.json create mode 100644 Emby.Server.Implementations/Localization/Core/id.json create mode 100644 Emby.Server.Implementations/Localization/Core/is.json create mode 100644 Emby.Server.Implementations/Localization/Core/it.json create mode 100644 Emby.Server.Implementations/Localization/Core/ja.json create mode 100644 Emby.Server.Implementations/Localization/Core/jbo.json create mode 100644 Emby.Server.Implementations/Localization/Core/ka.json create mode 100644 Emby.Server.Implementations/Localization/Core/kab.json create mode 100644 Emby.Server.Implementations/Localization/Core/kk.json create mode 100644 Emby.Server.Implementations/Localization/Core/km.json create mode 100644 Emby.Server.Implementations/Localization/Core/kn.json create mode 100644 Emby.Server.Implementations/Localization/Core/ko.json create mode 100644 Emby.Server.Implementations/Localization/Core/kw.json create mode 100644 Emby.Server.Implementations/Localization/Core/ky.json create mode 100644 Emby.Server.Implementations/Localization/Core/lb.json create mode 100644 Emby.Server.Implementations/Localization/Core/lt-LT.json create mode 100644 Emby.Server.Implementations/Localization/Core/lv.json create mode 100644 Emby.Server.Implementations/Localization/Core/lzh.json create mode 100644 Emby.Server.Implementations/Localization/Core/mi.json create mode 100644 Emby.Server.Implementations/Localization/Core/mk.json create mode 100644 Emby.Server.Implementations/Localization/Core/ml.json create mode 100644 Emby.Server.Implementations/Localization/Core/mn.json create mode 100644 Emby.Server.Implementations/Localization/Core/mr.json create mode 100644 Emby.Server.Implementations/Localization/Core/ms.json create mode 100644 Emby.Server.Implementations/Localization/Core/mt.json create mode 100644 Emby.Server.Implementations/Localization/Core/my.json create mode 100644 Emby.Server.Implementations/Localization/Core/nb.json create mode 100644 Emby.Server.Implementations/Localization/Core/ne.json create mode 100644 Emby.Server.Implementations/Localization/Core/nl.json create mode 100644 Emby.Server.Implementations/Localization/Core/nn.json create mode 100644 Emby.Server.Implementations/Localization/Core/oc.json create mode 100644 Emby.Server.Implementations/Localization/Core/or.json create mode 100644 Emby.Server.Implementations/Localization/Core/pa.json create mode 100644 Emby.Server.Implementations/Localization/Core/pl.json create mode 100644 Emby.Server.Implementations/Localization/Core/pr.json create mode 100644 Emby.Server.Implementations/Localization/Core/pt-BR.json create mode 100644 Emby.Server.Implementations/Localization/Core/pt-PT.json create mode 100644 Emby.Server.Implementations/Localization/Core/pt.json create mode 100644 Emby.Server.Implementations/Localization/Core/ro.json create mode 100644 Emby.Server.Implementations/Localization/Core/ru.json create mode 100644 Emby.Server.Implementations/Localization/Core/si.json create mode 100644 Emby.Server.Implementations/Localization/Core/sk.json create mode 100644 Emby.Server.Implementations/Localization/Core/sl-SI.json create mode 100644 Emby.Server.Implementations/Localization/Core/sn.json create mode 100644 Emby.Server.Implementations/Localization/Core/sq.json create mode 100644 Emby.Server.Implementations/Localization/Core/sr.json create mode 100644 Emby.Server.Implementations/Localization/Core/sv.json create mode 100644 Emby.Server.Implementations/Localization/Core/sw.json create mode 100644 Emby.Server.Implementations/Localization/Core/ta.json create mode 100644 Emby.Server.Implementations/Localization/Core/te.json create mode 100644 Emby.Server.Implementations/Localization/Core/th.json create mode 100644 Emby.Server.Implementations/Localization/Core/tr.json create mode 100644 Emby.Server.Implementations/Localization/Core/ug.json create mode 100644 Emby.Server.Implementations/Localization/Core/uk.json create mode 100644 Emby.Server.Implementations/Localization/Core/ur.json create mode 100644 Emby.Server.Implementations/Localization/Core/ur_PK.json create mode 100644 Emby.Server.Implementations/Localization/Core/uz.json create mode 100644 Emby.Server.Implementations/Localization/Core/vi.json create mode 100644 Emby.Server.Implementations/Localization/Core/zh-CN.json create mode 100644 Emby.Server.Implementations/Localization/Core/zh-HK.json create mode 100644 Emby.Server.Implementations/Localization/Core/zh-TW.json create mode 100644 Emby.Server.Implementations/Localization/Core/zu.json create mode 100644 Emby.Server.Implementations/Localization/LocalizationManager.cs create mode 100644 Emby.Server.Implementations/Localization/Ratings/0-prefer.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/ar.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/au.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/be.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/bg.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/br.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/ca.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/cl.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/co.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/cz.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/de.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/dk.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/es.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/fi.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/fr.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/gb.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/gr.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/hu.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/id.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/ie.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/in.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/it.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/jp.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/kr.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/kz.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/lt.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/mx.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/nl.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/no.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/nz.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/ph.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/pl.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/pt.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/ro.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/ru.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/se.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/sg.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/sk.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/th.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/tr.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/tw.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/ua.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/uk.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/us.json create mode 100644 Emby.Server.Implementations/Localization/Ratings/za.json create mode 100644 Emby.Server.Implementations/Localization/countries.json create mode 100644 Emby.Server.Implementations/Localization/iso6392.txt create mode 100644 Emby.Server.Implementations/Playlists/PlaylistManager.cs create mode 100644 Emby.Server.Implementations/Playlists/PlaylistsFolder.cs create mode 100644 Emby.Server.Implementations/Plugins/PluginLoadContext.cs create mode 100644 Emby.Server.Implementations/Plugins/PluginManager.cs create mode 100644 Emby.Server.Implementations/Properties/AssemblyInfo.cs create mode 100644 Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/TaskManager.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionAndPlaylistPathsTask.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/MediaSegmentExtractionTask.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs create mode 100644 Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs create mode 100644 Emby.Server.Implementations/Serialization/MyXmlSerializer.cs create mode 100644 Emby.Server.Implementations/ServerApplicationPaths.cs create mode 100644 Emby.Server.Implementations/Session/SessionManager.cs create mode 100644 Emby.Server.Implementations/Session/SessionWebSocketListener.cs create mode 100644 Emby.Server.Implementations/Session/WebSocketController.cs create mode 100644 Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/AlbumComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/ArtistComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/CriticRatingComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/DateCreatedComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/DatePlayedComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/IndexNumberComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/IsFolderComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/IsPlayedComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/NameComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/ParentIndexNumberComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/PlayCountComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/PremiereDateComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/ProductionYearComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/RandomComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/RuntimeComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/SortNameComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/StartDateComparer.cs create mode 100644 Emby.Server.Implementations/Sorting/StudioComparer.cs create mode 100644 Emby.Server.Implementations/SyncPlay/Group.cs create mode 100644 Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs create mode 100644 Emby.Server.Implementations/SystemManager.cs create mode 100644 Emby.Server.Implementations/TV/TVSeriesManager.cs create mode 100644 Emby.Server.Implementations/Updates/InstallationManager.cs create mode 100644 Emby.Server.Implementations/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 Emby.Server.Implementations/obj/Debug/net10.0/Emby.Server.Implementations.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 Emby.Server.Implementations/obj/Debug/net10.0/Emby.Server.Implementations.assets.cache create mode 100644 Emby.Server.Implementations/obj/Debug/net10.0/Emby.Server.Implementations.csproj.AssemblyReference.cache create mode 100644 Emby.Server.Implementations/obj/Emby.Server.Implementations.csproj.nuget.dgspec.json create mode 100644 Emby.Server.Implementations/obj/Emby.Server.Implementations.csproj.nuget.g.props create mode 100644 Emby.Server.Implementations/obj/Emby.Server.Implementations.csproj.nuget.g.targets create mode 100644 Emby.Server.Implementations/obj/project.assets.json create mode 100644 Emby.Server.Implementations/obj/project.nuget.cache create mode 100644 Emby.Server.Implementations/obj/project.packagespec.json create mode 100644 Emby.Server.Implementations/obj/rider.project.model.nuget.info create mode 100644 Emby.Server.Implementations/obj/rider.project.restore.info create mode 100644 Jellyfin.Api/Attributes/AcceptsFileAttribute.cs create mode 100644 Jellyfin.Api/Attributes/AcceptsImageFileAttribute.cs create mode 100644 Jellyfin.Api/Attributes/ParameterObsoleteAttribute.cs create mode 100644 Jellyfin.Api/Attributes/ProducesAudioFileAttribute.cs create mode 100644 Jellyfin.Api/Attributes/ProducesFileAttribute.cs create mode 100644 Jellyfin.Api/Attributes/ProducesImageFileAttribute.cs create mode 100644 Jellyfin.Api/Attributes/ProducesPlaylistFileAttribute.cs create mode 100644 Jellyfin.Api/Attributes/ProducesVideoFileAttribute.cs create mode 100644 Jellyfin.Api/Auth/AnonymousLanAccessPolicy/AnonymousLanAccessHandler.cs create mode 100644 Jellyfin.Api/Auth/AnonymousLanAccessPolicy/AnonymousLanAccessRequirement.cs create mode 100644 Jellyfin.Api/Auth/CustomAuthenticationHandler.cs create mode 100644 Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs create mode 100644 Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationRequirement.cs create mode 100644 Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs create mode 100644 Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupRequirement.cs create mode 100644 Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs create mode 100644 Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs create mode 100644 Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs create mode 100644 Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessRequirement.cs create mode 100644 Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionHandler.cs create mode 100644 Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionRequirement.cs create mode 100644 Jellyfin.Api/BaseJellyfinApiController.cs create mode 100644 Jellyfin.Api/Constants/AuthenticationSchemes.cs create mode 100644 Jellyfin.Api/Constants/InternalClaimTypes.cs create mode 100644 Jellyfin.Api/Constants/UserRoles.cs create mode 100644 Jellyfin.Api/Controllers/ActivityLogController.cs create mode 100644 Jellyfin.Api/Controllers/ApiKeyController.cs create mode 100644 Jellyfin.Api/Controllers/ArtistsController.cs create mode 100644 Jellyfin.Api/Controllers/AudioController.cs create mode 100644 Jellyfin.Api/Controllers/BackupController.cs create mode 100644 Jellyfin.Api/Controllers/BrandingController.cs create mode 100644 Jellyfin.Api/Controllers/ChannelsController.cs create mode 100644 Jellyfin.Api/Controllers/ClientLogController.cs create mode 100644 Jellyfin.Api/Controllers/CollectionController.cs create mode 100644 Jellyfin.Api/Controllers/ConfigurationController.cs create mode 100644 Jellyfin.Api/Controllers/DashboardController.cs create mode 100644 Jellyfin.Api/Controllers/DevicesController.cs create mode 100644 Jellyfin.Api/Controllers/DisplayPreferencesController.cs create mode 100644 Jellyfin.Api/Controllers/DynamicHlsController.cs create mode 100644 Jellyfin.Api/Controllers/EnvironmentController.cs create mode 100644 Jellyfin.Api/Controllers/FilterController.cs create mode 100644 Jellyfin.Api/Controllers/GenresController.cs create mode 100644 Jellyfin.Api/Controllers/HlsSegmentController.cs create mode 100644 Jellyfin.Api/Controllers/ImageController.cs create mode 100644 Jellyfin.Api/Controllers/InstantMixController.cs create mode 100644 Jellyfin.Api/Controllers/ItemLookupController.cs create mode 100644 Jellyfin.Api/Controllers/ItemRefreshController.cs create mode 100644 Jellyfin.Api/Controllers/ItemUpdateController.cs create mode 100644 Jellyfin.Api/Controllers/ItemsController.cs create mode 100644 Jellyfin.Api/Controllers/LibraryController.cs create mode 100644 Jellyfin.Api/Controllers/LibraryStructureController.cs create mode 100644 Jellyfin.Api/Controllers/LiveTvController.cs create mode 100644 Jellyfin.Api/Controllers/LocalizationController.cs create mode 100644 Jellyfin.Api/Controllers/LyricsController.cs create mode 100644 Jellyfin.Api/Controllers/MediaInfoController.cs create mode 100644 Jellyfin.Api/Controllers/MediaSegmentsController.cs create mode 100644 Jellyfin.Api/Controllers/MoviesController.cs create mode 100644 Jellyfin.Api/Controllers/MusicGenresController.cs create mode 100644 Jellyfin.Api/Controllers/PackageController.cs create mode 100644 Jellyfin.Api/Controllers/PersonsController.cs create mode 100644 Jellyfin.Api/Controllers/PlaylistsController.cs create mode 100644 Jellyfin.Api/Controllers/PlaystateController.cs create mode 100644 Jellyfin.Api/Controllers/PluginsController.cs create mode 100644 Jellyfin.Api/Controllers/QuickConnectController.cs create mode 100644 Jellyfin.Api/Controllers/RemoteImageController.cs create mode 100644 Jellyfin.Api/Controllers/ScheduledTasksController.cs create mode 100644 Jellyfin.Api/Controllers/SearchController.cs create mode 100644 Jellyfin.Api/Controllers/SessionController.cs create mode 100644 Jellyfin.Api/Controllers/StartupController.cs create mode 100644 Jellyfin.Api/Controllers/StudiosController.cs create mode 100644 Jellyfin.Api/Controllers/SubtitleController.cs create mode 100644 Jellyfin.Api/Controllers/SuggestionsController.cs create mode 100644 Jellyfin.Api/Controllers/SyncPlayController.cs create mode 100644 Jellyfin.Api/Controllers/SystemController.cs create mode 100644 Jellyfin.Api/Controllers/TimeSyncController.cs create mode 100644 Jellyfin.Api/Controllers/TrailersController.cs create mode 100644 Jellyfin.Api/Controllers/TrickplayController.cs create mode 100644 Jellyfin.Api/Controllers/TvShowsController.cs create mode 100644 Jellyfin.Api/Controllers/UniversalAudioController.cs create mode 100644 Jellyfin.Api/Controllers/UserController.cs create mode 100644 Jellyfin.Api/Controllers/UserLibraryController.cs create mode 100644 Jellyfin.Api/Controllers/UserViewsController.cs create mode 100644 Jellyfin.Api/Controllers/VideoAttachmentsController.cs create mode 100644 Jellyfin.Api/Controllers/VideosController.cs create mode 100644 Jellyfin.Api/Controllers/YearsController.cs create mode 100644 Jellyfin.Api/Extensions/ClaimsPrincipalExtensions.cs create mode 100644 Jellyfin.Api/Extensions/DtoExtensions.cs create mode 100644 Jellyfin.Api/Formatters/CamelCaseJsonProfileFormatter.cs create mode 100644 Jellyfin.Api/Formatters/CssOutputFormatter.cs create mode 100644 Jellyfin.Api/Formatters/PascalCaseJsonProfileFormatter.cs create mode 100644 Jellyfin.Api/Formatters/XmlOutputFormatter.cs create mode 100644 Jellyfin.Api/Helpers/AudioHelper.cs create mode 100644 Jellyfin.Api/Helpers/DynamicHlsHelper.cs create mode 100644 Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs create mode 100644 Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs create mode 100644 Jellyfin.Api/Helpers/HlsHelpers.cs create mode 100644 Jellyfin.Api/Helpers/MediaInfoHelper.cs create mode 100644 Jellyfin.Api/Helpers/RequestHelpers.cs create mode 100644 Jellyfin.Api/Helpers/StreamingHelpers.cs create mode 100644 Jellyfin.Api/Jellyfin.Api.csproj create mode 100644 Jellyfin.Api/Middleware/BaseUrlRedirectionMiddleware.cs create mode 100644 Jellyfin.Api/Middleware/ExceptionMiddleware.cs create mode 100644 Jellyfin.Api/Middleware/IpBasedAccessValidationMiddleware.cs create mode 100644 Jellyfin.Api/Middleware/QueryStringDecodingMiddleware.cs create mode 100644 Jellyfin.Api/Middleware/ResponseTimeMiddleware.cs create mode 100644 Jellyfin.Api/Middleware/RobotsRedirectionMiddleware.cs create mode 100644 Jellyfin.Api/Middleware/ServerStartupMessageMiddleware.cs create mode 100644 Jellyfin.Api/Middleware/UrlDecodeQueryFeature.cs create mode 100644 Jellyfin.Api/Middleware/WebSocketHandlerMiddleware.cs create mode 100644 Jellyfin.Api/ModelBinders/CommaDelimitedCollectionModelBinder.cs create mode 100644 Jellyfin.Api/ModelBinders/LegacyDateTimeModelBinder.cs create mode 100644 Jellyfin.Api/ModelBinders/NullableEnumModelBinder.cs create mode 100644 Jellyfin.Api/ModelBinders/NullableEnumModelBinderProvider.cs create mode 100644 Jellyfin.Api/ModelBinders/PipeDelimitedCollectionModelBinder.cs create mode 100644 Jellyfin.Api/Models/ClientLogDtos/ClientLogDocumentResponseDto.cs create mode 100644 Jellyfin.Api/Models/ConfigurationPageInfo.cs create mode 100644 Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfoDto.cs create mode 100644 Jellyfin.Api/Models/EnvironmentDtos/ValidatePathDto.cs create mode 100644 Jellyfin.Api/Models/LibraryDtos/LibraryOptionInfoDto.cs create mode 100644 Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs create mode 100644 Jellyfin.Api/Models/LibraryDtos/LibraryTypeOptionsDto.cs create mode 100644 Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs create mode 100644 Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs create mode 100644 Jellyfin.Api/Models/LibraryStructureDto/AddVirtualFolderDto.cs create mode 100644 Jellyfin.Api/Models/LibraryStructureDto/MediaPathDto.cs create mode 100644 Jellyfin.Api/Models/LibraryStructureDto/UpdateLibraryOptionsDto.cs create mode 100644 Jellyfin.Api/Models/LibraryStructureDto/UpdateMediaPathRequestDto.cs create mode 100644 Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs create mode 100644 Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs create mode 100644 Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs create mode 100644 Jellyfin.Api/Models/MediaInfoDtos/PlaybackInfoDto.cs create mode 100644 Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs create mode 100644 Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistDto.cs create mode 100644 Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistUserDto.cs create mode 100644 Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs create mode 100644 Jellyfin.Api/Models/StartupDtos/StartupRemoteAccessDto.cs create mode 100644 Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs create mode 100644 Jellyfin.Api/Models/StreamingDtos/HlsAudioRequestDto.cs create mode 100644 Jellyfin.Api/Models/StreamingDtos/HlsVideoRequestDto.cs create mode 100644 Jellyfin.Api/Models/SubtitleDtos/UploadSubtitleDto.cs create mode 100644 Jellyfin.Api/Models/SyncPlayDtos/BufferRequestDto.cs create mode 100644 Jellyfin.Api/Models/SyncPlayDtos/IgnoreWaitRequestDto.cs create mode 100644 Jellyfin.Api/Models/SyncPlayDtos/JoinGroupRequestDto.cs create mode 100644 Jellyfin.Api/Models/SyncPlayDtos/MovePlaylistItemRequestDto.cs create mode 100644 Jellyfin.Api/Models/SyncPlayDtos/NewGroupRequestDto.cs create mode 100644 Jellyfin.Api/Models/SyncPlayDtos/NextItemRequestDto.cs create mode 100644 Jellyfin.Api/Models/SyncPlayDtos/PingRequestDto.cs create mode 100644 Jellyfin.Api/Models/SyncPlayDtos/PlayRequestDto.cs create mode 100644 Jellyfin.Api/Models/SyncPlayDtos/PreviousItemRequestDto.cs create mode 100644 Jellyfin.Api/Models/SyncPlayDtos/QueueRequestDto.cs create mode 100644 Jellyfin.Api/Models/SyncPlayDtos/ReadyRequestDto.cs create mode 100644 Jellyfin.Api/Models/SyncPlayDtos/RemoveFromPlaylistRequestDto.cs create mode 100644 Jellyfin.Api/Models/SyncPlayDtos/SeekRequestDto.cs create mode 100644 Jellyfin.Api/Models/SyncPlayDtos/SetPlaylistItemRequestDto.cs create mode 100644 Jellyfin.Api/Models/SyncPlayDtos/SetRepeatModeRequestDto.cs create mode 100644 Jellyfin.Api/Models/SyncPlayDtos/SetShuffleModeRequestDto.cs create mode 100644 Jellyfin.Api/Models/SystemInfoDtos/FolderStorageDto.cs create mode 100644 Jellyfin.Api/Models/SystemInfoDtos/LibraryStorageDto.cs create mode 100644 Jellyfin.Api/Models/SystemInfoDtos/SystemStorageDto.cs create mode 100644 Jellyfin.Api/Models/UserDtos/AuthenticateUserByName.cs create mode 100644 Jellyfin.Api/Models/UserDtos/CreateUserByName.cs create mode 100644 Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs create mode 100644 Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs create mode 100644 Jellyfin.Api/Models/UserDtos/QuickConnectDto.cs create mode 100644 Jellyfin.Api/Models/UserDtos/UpdateUserPassword.cs create mode 100644 Jellyfin.Api/Models/UserViewDtos/SpecialViewOptionDto.cs create mode 100644 Jellyfin.Api/Results/OkResultOfT.cs create mode 100644 Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs create mode 100644 Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs create mode 100644 Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs create mode 100644 Jellyfin.Api/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 Jellyfin.Api/obj/Debug/net10.0/Jellyfin.Api.AssemblyInfo.cs create mode 100644 Jellyfin.Api/obj/Debug/net10.0/Jellyfin.Api.AssemblyInfoInputs.cache create mode 100644 Jellyfin.Api/obj/Debug/net10.0/Jellyfin.Api.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 Jellyfin.Api/obj/Debug/net10.0/Jellyfin.Api.assets.cache create mode 100644 Jellyfin.Api/obj/Debug/net10.0/Jellyfin.Api.csproj.AssemblyReference.cache create mode 100644 Jellyfin.Api/obj/Jellyfin.Api.csproj.nuget.dgspec.json create mode 100644 Jellyfin.Api/obj/Jellyfin.Api.csproj.nuget.g.props create mode 100644 Jellyfin.Api/obj/Jellyfin.Api.csproj.nuget.g.targets create mode 100644 Jellyfin.Api/obj/project.assets.json create mode 100644 Jellyfin.Api/obj/project.nuget.cache create mode 100644 Jellyfin.Api/obj/project.packagespec.json create mode 100644 Jellyfin.Api/obj/rider.project.model.nuget.info create mode 100644 Jellyfin.Api/obj/rider.project.restore.info create mode 100644 Jellyfin.Data/Attributes/OpenApiIgnoreEnumAttribute.cs create mode 100644 Jellyfin.Data/DayOfWeekHelper.cs create mode 100644 Jellyfin.Data/Dtos/DeviceOptionsDto.cs create mode 100644 Jellyfin.Data/Enums/ActivityLogSortBy.cs create mode 100644 Jellyfin.Data/Enums/AudioSpatialFormat.cs create mode 100644 Jellyfin.Data/Enums/BaseItemKind.cs create mode 100644 Jellyfin.Data/Enums/CollectionType.cs create mode 100644 Jellyfin.Data/Enums/ItemSortBy.cs create mode 100644 Jellyfin.Data/Enums/MediaStreamProtocol.cs create mode 100644 Jellyfin.Data/Enums/MediaType.cs create mode 100644 Jellyfin.Data/Enums/PersonKind.cs create mode 100644 Jellyfin.Data/Enums/SyncPlayAccessRequirementType.cs create mode 100644 Jellyfin.Data/Enums/UnratedItem.cs create mode 100644 Jellyfin.Data/Enums/VideoRange.cs create mode 100644 Jellyfin.Data/Enums/VideoRangeType.cs create mode 100644 Jellyfin.Data/Events/GenericEventArgs.cs create mode 100644 Jellyfin.Data/Events/System/PendingRestartEventArgs.cs create mode 100644 Jellyfin.Data/Events/Users/UserCreatedEventArgs.cs create mode 100644 Jellyfin.Data/Events/Users/UserDeletedEventArgs.cs create mode 100644 Jellyfin.Data/Events/Users/UserLockedOutEventArgs.cs create mode 100644 Jellyfin.Data/Events/Users/UserPasswordChangedEventArgs.cs create mode 100644 Jellyfin.Data/Events/Users/UserUpdatedEventArgs.cs create mode 100644 Jellyfin.Data/Jellyfin.Data.csproj create mode 100644 Jellyfin.Data/Queries/ActivityLogQuery.cs create mode 100644 Jellyfin.Data/Queries/DeviceQuery.cs create mode 100644 Jellyfin.Data/Queries/PaginatedQuery.cs create mode 100644 Jellyfin.Data/UserEntityExtensions.cs create mode 100644 Jellyfin.Data/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 Jellyfin.Data/obj/Debug/net10.0/Jellyfin.Data.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 Jellyfin.Data/obj/Debug/net10.0/Jellyfin.Data.assets.cache create mode 100644 Jellyfin.Data/obj/Debug/net10.0/Jellyfin.Data.csproj.AssemblyReference.cache create mode 100644 Jellyfin.Data/obj/Jellyfin.Data.csproj.nuget.dgspec.json create mode 100644 Jellyfin.Data/obj/Jellyfin.Data.csproj.nuget.g.props create mode 100644 Jellyfin.Data/obj/Jellyfin.Data.csproj.nuget.g.targets create mode 100644 Jellyfin.Data/obj/project.assets.json create mode 100644 Jellyfin.Data/obj/project.nuget.cache create mode 100644 Jellyfin.Data/obj/project.packagespec.json create mode 100644 Jellyfin.Data/obj/rider.project.model.nuget.info create mode 100644 Jellyfin.Data/obj/rider.project.restore.info create mode 100644 Jellyfin.Server.Implementations/Activity/ActivityManager.cs create mode 100644 Jellyfin.Server.Implementations/DbConfiguration/DatabaseConfigurationFactory.cs create mode 100644 Jellyfin.Server.Implementations/DbConfiguration/DatabaseConfigurationStore.cs create mode 100644 Jellyfin.Server.Implementations/Devices/DeviceManager.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Library/LyricDownloadFailureLogger.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Library/SubtitleDownloadFailureLogger.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationFailedLogger.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Session/SessionEndedLogger.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Session/SessionStartedLogger.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/System/PendingRestartNotifier.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedLogger.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledLogger.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUpdatedLogger.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Users/UserCreatedLogger.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedLogger.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Users/UserLockedOutLogger.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Users/UserPasswordChangedLogger.cs create mode 100644 Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs create mode 100644 Jellyfin.Server.Implementations/Events/EventManager.cs create mode 100644 Jellyfin.Server.Implementations/Events/EventingServiceCollectionExtensions.cs create mode 100644 Jellyfin.Server.Implementations/Extensions/ExpressionExtensions.cs create mode 100644 Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs create mode 100644 Jellyfin.Server.Implementations/FullSystemBackup/BackupManifest.cs create mode 100644 Jellyfin.Server.Implementations/FullSystemBackup/BackupOptions.cs create mode 100644 Jellyfin.Server.Implementations/FullSystemBackup/BackupService.cs create mode 100644 Jellyfin.Server.Implementations/Item/BaseItemRepository.cs create mode 100644 Jellyfin.Server.Implementations/Item/ChapterRepository.cs create mode 100644 Jellyfin.Server.Implementations/Item/KeyframeRepository.cs create mode 100644 Jellyfin.Server.Implementations/Item/MediaAttachmentRepository.cs create mode 100644 Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs create mode 100644 Jellyfin.Server.Implementations/Item/OrderMapper.cs create mode 100644 Jellyfin.Server.Implementations/Item/PeopleRepository.cs create mode 100644 Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj create mode 100644 Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs create mode 100644 Jellyfin.Server.Implementations/Properties/AssemblyInfo.cs create mode 100644 Jellyfin.Server.Implementations/Security/AuthenticationManager.cs create mode 100644 Jellyfin.Server.Implementations/Security/AuthorizationContext.cs create mode 100644 Jellyfin.Server.Implementations/StorageHelpers/StorageHelper.cs create mode 100644 Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs create mode 100644 Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs create mode 100644 Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs create mode 100644 Jellyfin.Server.Implementations/Users/DeviceAccessHost.cs create mode 100644 Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs create mode 100644 Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs create mode 100644 Jellyfin.Server.Implementations/Users/UserManager.cs create mode 100644 Jellyfin.Server.Implementations/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 Jellyfin.Server.Implementations/obj/Debug/net10.0/Jellyfin.Server.Implementations.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 Jellyfin.Server.Implementations/obj/Debug/net10.0/Jellyfin.Server.Implementations.assets.cache create mode 100644 Jellyfin.Server.Implementations/obj/Debug/net10.0/Jellyfin.Server.Implementations.csproj.AssemblyReference.cache create mode 100644 Jellyfin.Server.Implementations/obj/Jellyfin.Server.Implementations.csproj.nuget.dgspec.json create mode 100644 Jellyfin.Server.Implementations/obj/Jellyfin.Server.Implementations.csproj.nuget.g.props create mode 100644 Jellyfin.Server.Implementations/obj/Jellyfin.Server.Implementations.csproj.nuget.g.targets create mode 100644 Jellyfin.Server.Implementations/obj/project.assets.json create mode 100644 Jellyfin.Server.Implementations/obj/project.nuget.cache create mode 100644 Jellyfin.Server.Implementations/obj/project.packagespec.json create mode 100644 Jellyfin.Server.Implementations/obj/rider.project.model.nuget.info create mode 100644 Jellyfin.Server.Implementations/obj/rider.project.restore.info create mode 100644 Jellyfin.Server/Configuration/CorsPolicyProvider.cs create mode 100644 Jellyfin.Server/CoreAppHost.cs create mode 100644 Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs create mode 100644 Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs create mode 100644 Jellyfin.Server/Extensions/WebHostBuilderExtensions.cs create mode 100644 Jellyfin.Server/Filters/AdditionalModelFilter.cs create mode 100644 Jellyfin.Server/Filters/CachingOpenApiProvider.cs create mode 100644 Jellyfin.Server/Filters/FileRequestFilter.cs create mode 100644 Jellyfin.Server/Filters/FileResponseFilter.cs create mode 100644 Jellyfin.Server/Filters/FlagsEnumSchemaFilter.cs create mode 100644 Jellyfin.Server/Filters/IgnoreEnumSchemaFilter.cs create mode 100644 Jellyfin.Server/Filters/ParameterObsoleteFilter.cs create mode 100644 Jellyfin.Server/Filters/RetryOnTemporarilyUnavailableFilter.cs create mode 100644 Jellyfin.Server/Filters/SecurityRequirementsOperationFilter.cs create mode 100644 Jellyfin.Server/HealthChecks/DbContextFactoryHealthCheck.cs create mode 100644 Jellyfin.Server/Helpers/StartupHelpers.cs create mode 100644 Jellyfin.Server/Jellyfin.Server.csproj create mode 100644 Jellyfin.Server/Jellyfin.Server.ico create mode 100644 Jellyfin.Server/Migrations/IAsyncMigrationRoutine.cs create mode 100644 Jellyfin.Server/Migrations/IDatabaseMigrationRoutine.cs create mode 100644 Jellyfin.Server/Migrations/JellyfinMigrationAttribute.cs create mode 100644 Jellyfin.Server/Migrations/JellyfinMigrationBackupAttribute.cs create mode 100644 Jellyfin.Server/Migrations/JellyfinMigrationService.cs create mode 100644 Jellyfin.Server/Migrations/MigrationOptions.cs create mode 100644 Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs create mode 100644 Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs create mode 100644 Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs create mode 100644 Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs create mode 100644 Jellyfin.Server/Migrations/PreStartupRoutines/RenameEnableGroupingIntoCollections.cs create mode 100644 Jellyfin.Server/Migrations/Routines/AddDefaultCastReceivers.cs create mode 100644 Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs create mode 100644 Jellyfin.Server/Migrations/Routines/CleanMusicArtist.cs create mode 100644 Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs create mode 100644 Jellyfin.Server/Migrations/Routines/DisableLegacyAuthorization.cs create mode 100644 Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs create mode 100644 Jellyfin.Server/Migrations/Routines/FixAudioData.cs create mode 100644 Jellyfin.Server/Migrations/Routines/FixDates.cs create mode 100644 Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs create mode 100644 Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs create mode 100644 Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs create mode 100644 Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs create mode 100644 Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs create mode 100644 Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs create mode 100644 Jellyfin.Server/Migrations/Routines/MigrateLibraryDbCompatibilityCheck.cs create mode 100644 Jellyfin.Server/Migrations/Routines/MigrateLibraryUserData.cs create mode 100644 Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs create mode 100644 Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs create mode 100644 Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs create mode 100644 Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs create mode 100644 Jellyfin.Server/Migrations/Routines/ReaddDefaultPluginRepository.cs create mode 100644 Jellyfin.Server/Migrations/Routines/RefreshCleanNames.cs create mode 100644 Jellyfin.Server/Migrations/Routines/RefreshInternalDateModified.cs create mode 100644 Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs create mode 100644 Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs create mode 100644 Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs create mode 100644 Jellyfin.Server/Migrations/Routines/ReseedFolderFlag.cs create mode 100644 Jellyfin.Server/Migrations/Routines/UpdateDefaultPluginRepository.cs create mode 100644 Jellyfin.Server/Migrations/Stages/CodeMigration.cs create mode 100644 Jellyfin.Server/Migrations/Stages/JellyfinMigrationStageTypes.cs create mode 100644 Jellyfin.Server/Migrations/Stages/MigrationStage.cs create mode 100644 Jellyfin.Server/Program.cs create mode 100644 Jellyfin.Server/Properties/AssemblyInfo.cs create mode 100644 Jellyfin.Server/Properties/launchSettings.json create mode 100644 Jellyfin.Server/Resources/Configuration/logging.json create mode 100644 Jellyfin.Server/ServerSetupApp/IStartupLogger.cs create mode 100644 Jellyfin.Server/ServerSetupApp/SetupServer.cs create mode 100644 Jellyfin.Server/ServerSetupApp/StartupLogTopic.cs create mode 100644 Jellyfin.Server/ServerSetupApp/StartupLogger.cs create mode 100644 Jellyfin.Server/ServerSetupApp/StartupLoggerExtensions.cs create mode 100644 Jellyfin.Server/ServerSetupApp/StartupLoggerOfCategory.cs create mode 100644 Jellyfin.Server/ServerSetupApp/index.mstemplate.html create mode 100644 Jellyfin.Server/Startup.cs create mode 100644 Jellyfin.Server/StartupOptions.cs create mode 100644 Jellyfin.Server/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 Jellyfin.Server/obj/Debug/net10.0/Jellyfin.Server.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 Jellyfin.Server/obj/Debug/net10.0/Jellyfin.Server.assets.cache create mode 100644 Jellyfin.Server/obj/Debug/net10.0/Jellyfin.Server.csproj.AssemblyReference.cache create mode 100644 Jellyfin.Server/obj/Jellyfin.Server.csproj.nuget.dgspec.json create mode 100644 Jellyfin.Server/obj/Jellyfin.Server.csproj.nuget.g.props create mode 100644 Jellyfin.Server/obj/Jellyfin.Server.csproj.nuget.g.targets create mode 100644 Jellyfin.Server/obj/project.assets.json create mode 100644 Jellyfin.Server/obj/project.nuget.cache create mode 100644 Jellyfin.Server/obj/project.packagespec.json create mode 100644 Jellyfin.Server/obj/rider.project.model.nuget.info create mode 100644 Jellyfin.Server/obj/rider.project.restore.info create mode 100644 Jellyfin.Server/wwwroot/api-docs/jellyfin.svg create mode 100644 Jellyfin.Server/wwwroot/api-docs/redoc/custom.css create mode 100644 Jellyfin.Server/wwwroot/api-docs/swagger/custom.css create mode 100644 Jellyfin.sln create mode 100644 Jellyfin.sln.DotSettings create mode 100644 LICENSE create mode 100644 MediaBrowser.Common/Api/Policies.cs create mode 100644 MediaBrowser.Common/Configuration/ConfigurationStore.cs create mode 100644 MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs create mode 100644 MediaBrowser.Common/Configuration/EncodingConfigurationExtensions.cs create mode 100644 MediaBrowser.Common/Configuration/IApplicationPaths.cs create mode 100644 MediaBrowser.Common/Configuration/IConfigurationFactory.cs create mode 100644 MediaBrowser.Common/Configuration/IConfigurationManager.cs create mode 100644 MediaBrowser.Common/Configuration/IValidatingConfiguration.cs create mode 100644 MediaBrowser.Common/Crc32.cs create mode 100644 MediaBrowser.Common/Events/EventHelper.cs create mode 100644 MediaBrowser.Common/Extensions/BaseExtensions.cs create mode 100644 MediaBrowser.Common/Extensions/HttpContextExtensions.cs create mode 100644 MediaBrowser.Common/Extensions/MethodNotAllowedException.cs create mode 100644 MediaBrowser.Common/Extensions/ProcessExtensions.cs create mode 100644 MediaBrowser.Common/Extensions/RateLimitExceededException.cs create mode 100644 MediaBrowser.Common/Extensions/ResourceNotFoundException.cs create mode 100644 MediaBrowser.Common/FfmpegException.cs create mode 100644 MediaBrowser.Common/IApplicationHost.cs create mode 100644 MediaBrowser.Common/MediaBrowser.Common.csproj create mode 100644 MediaBrowser.Common/Net/INetworkManager.cs create mode 100644 MediaBrowser.Common/Net/NamedClient.cs create mode 100644 MediaBrowser.Common/Net/NetworkConfiguration.cs create mode 100644 MediaBrowser.Common/Net/NetworkConfigurationExtensions.cs create mode 100644 MediaBrowser.Common/Net/NetworkConfigurationFactory.cs create mode 100644 MediaBrowser.Common/Net/NetworkConfigurationStore.cs create mode 100644 MediaBrowser.Common/Net/NetworkConstants.cs create mode 100644 MediaBrowser.Common/Net/NetworkUtils.cs create mode 100644 MediaBrowser.Common/Net/RemoteAccessPolicyResult.cs create mode 100644 MediaBrowser.Common/Plugins/BasePlugin.cs create mode 100644 MediaBrowser.Common/Plugins/BasePluginOfT.cs create mode 100644 MediaBrowser.Common/Plugins/IHasPluginConfiguration.cs create mode 100644 MediaBrowser.Common/Plugins/IPlugin.cs create mode 100644 MediaBrowser.Common/Plugins/IPluginAssembly.cs create mode 100644 MediaBrowser.Common/Plugins/IPluginManager.cs create mode 100644 MediaBrowser.Common/Plugins/LocalPlugin.cs create mode 100644 MediaBrowser.Common/Plugins/PluginManifest.cs create mode 100644 MediaBrowser.Common/Properties/AssemblyInfo.cs create mode 100644 MediaBrowser.Common/Providers/ProviderIdParsers.cs create mode 100644 MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs create mode 100644 MediaBrowser.Common/RequiresSourceSerialisationAttribute.cs create mode 100644 MediaBrowser.Common/Updates/IInstallationManager.cs create mode 100644 MediaBrowser.Common/Updates/InstallationEventArgs.cs create mode 100644 MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs create mode 100644 MediaBrowser.Common/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 MediaBrowser.Common/obj/Debug/net10.0/MediaBrowser.Common.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 MediaBrowser.Common/obj/Debug/net10.0/MediaBrowser.Common.assets.cache create mode 100644 MediaBrowser.Common/obj/Debug/net10.0/MediaBrowser.Common.csproj.AssemblyReference.cache create mode 100644 MediaBrowser.Common/obj/MediaBrowser.Common.csproj.nuget.dgspec.json create mode 100644 MediaBrowser.Common/obj/MediaBrowser.Common.csproj.nuget.g.props create mode 100644 MediaBrowser.Common/obj/MediaBrowser.Common.csproj.nuget.g.targets create mode 100644 MediaBrowser.Common/obj/project.assets.json create mode 100644 MediaBrowser.Common/obj/project.nuget.cache create mode 100644 MediaBrowser.Common/obj/project.packagespec.json create mode 100644 MediaBrowser.Common/obj/rider.project.model.nuget.info create mode 100644 MediaBrowser.Common/obj/rider.project.restore.info create mode 100644 MediaBrowser.Controller/Authentication/AuthenticationException.cs create mode 100644 MediaBrowser.Controller/Authentication/AuthenticationResult.cs create mode 100644 MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs create mode 100644 MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs create mode 100644 MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs create mode 100644 MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs create mode 100644 MediaBrowser.Controller/Channels/Channel.cs create mode 100644 MediaBrowser.Controller/Channels/ChannelItemInfo.cs create mode 100644 MediaBrowser.Controller/Channels/ChannelItemResult.cs create mode 100644 MediaBrowser.Controller/Channels/ChannelItemType.cs create mode 100644 MediaBrowser.Controller/Channels/ChannelLatestMediaSearch.cs create mode 100644 MediaBrowser.Controller/Channels/ChannelParentalRating.cs create mode 100644 MediaBrowser.Controller/Channels/ChannelSearchInfo.cs create mode 100644 MediaBrowser.Controller/Channels/IChannel.cs create mode 100644 MediaBrowser.Controller/Channels/IChannelManager.cs create mode 100644 MediaBrowser.Controller/Channels/IDisableMediaSourceDisplay.cs create mode 100644 MediaBrowser.Controller/Channels/IHasCacheKey.cs create mode 100644 MediaBrowser.Controller/Channels/IHasFolderAttributes.cs create mode 100644 MediaBrowser.Controller/Channels/IRequiresMediaInfoCallback.cs create mode 100644 MediaBrowser.Controller/Channels/ISupportsDelete.cs create mode 100644 MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs create mode 100644 MediaBrowser.Controller/Channels/ISupportsMediaProbe.cs create mode 100644 MediaBrowser.Controller/Channels/InternalChannelFeatures.cs create mode 100644 MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs create mode 100644 MediaBrowser.Controller/Chapters/IChapterManager.cs create mode 100644 MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs create mode 100644 MediaBrowser.Controller/ClientEvent/IClientEventLogger.cs create mode 100644 MediaBrowser.Controller/Collections/CollectionCreatedEventArgs.cs create mode 100644 MediaBrowser.Controller/Collections/CollectionCreationOptions.cs create mode 100644 MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs create mode 100644 MediaBrowser.Controller/Collections/ICollectionManager.cs create mode 100644 MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs create mode 100644 MediaBrowser.Controller/Devices/IDeviceManager.cs create mode 100644 MediaBrowser.Controller/Drawing/IImageEncoder.cs create mode 100644 MediaBrowser.Controller/Drawing/IImageProcessor.cs create mode 100644 MediaBrowser.Controller/Drawing/ImageCollageOptions.cs create mode 100644 MediaBrowser.Controller/Drawing/ImageHelper.cs create mode 100644 MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs create mode 100644 MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs create mode 100644 MediaBrowser.Controller/Dto/DtoOptions.cs create mode 100644 MediaBrowser.Controller/Dto/IDtoService.cs create mode 100644 MediaBrowser.Controller/Entities/AggregateFolder.cs create mode 100644 MediaBrowser.Controller/Entities/Audio/Audio.cs create mode 100644 MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs create mode 100644 MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs create mode 100644 MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs create mode 100644 MediaBrowser.Controller/Entities/Audio/MusicArtist.cs create mode 100644 MediaBrowser.Controller/Entities/Audio/MusicGenre.cs create mode 100644 MediaBrowser.Controller/Entities/AudioBook.cs create mode 100644 MediaBrowser.Controller/Entities/BaseItem.cs create mode 100644 MediaBrowser.Controller/Entities/BaseItemExtensions.cs create mode 100644 MediaBrowser.Controller/Entities/BasePluginFolder.cs create mode 100644 MediaBrowser.Controller/Entities/Book.cs create mode 100644 MediaBrowser.Controller/Entities/CollectionFolder.cs create mode 100644 MediaBrowser.Controller/Entities/Extensions.cs create mode 100644 MediaBrowser.Controller/Entities/Folder.cs create mode 100644 MediaBrowser.Controller/Entities/Genre.cs create mode 100644 MediaBrowser.Controller/Entities/ICollectionFolder.cs create mode 100644 MediaBrowser.Controller/Entities/IHasAspectRatio.cs create mode 100644 MediaBrowser.Controller/Entities/IHasDisplayOrder.cs create mode 100644 MediaBrowser.Controller/Entities/IHasMediaSources.cs create mode 100644 MediaBrowser.Controller/Entities/IHasProgramAttributes.cs create mode 100644 MediaBrowser.Controller/Entities/IHasSeries.cs create mode 100644 MediaBrowser.Controller/Entities/IHasSpecialFeatures.cs create mode 100644 MediaBrowser.Controller/Entities/IHasStartDate.cs create mode 100644 MediaBrowser.Controller/Entities/IHasTrailers.cs create mode 100644 MediaBrowser.Controller/Entities/IItemByName.cs create mode 100644 MediaBrowser.Controller/Entities/IMetadataContainer.cs create mode 100644 MediaBrowser.Controller/Entities/ISupportsBoxSetGrouping.cs create mode 100644 MediaBrowser.Controller/Entities/ISupportsPlaceHolders.cs create mode 100644 MediaBrowser.Controller/Entities/InternalItemsQuery.cs create mode 100644 MediaBrowser.Controller/Entities/InternalPeopleQuery.cs create mode 100644 MediaBrowser.Controller/Entities/ItemImageInfo.cs create mode 100644 MediaBrowser.Controller/Entities/LinkedChild.cs create mode 100644 MediaBrowser.Controller/Entities/LinkedChildComparer.cs create mode 100644 MediaBrowser.Controller/Entities/LinkedChildType.cs create mode 100644 MediaBrowser.Controller/Entities/MediaSourceWidthComparator.cs create mode 100644 MediaBrowser.Controller/Entities/Movies/BoxSet.cs create mode 100644 MediaBrowser.Controller/Entities/Movies/Movie.cs create mode 100644 MediaBrowser.Controller/Entities/MusicVideo.cs create mode 100644 MediaBrowser.Controller/Entities/PeopleHelper.cs create mode 100644 MediaBrowser.Controller/Entities/Person.cs create mode 100644 MediaBrowser.Controller/Entities/PersonInfo.cs create mode 100644 MediaBrowser.Controller/Entities/Photo.cs create mode 100644 MediaBrowser.Controller/Entities/PhotoAlbum.cs create mode 100644 MediaBrowser.Controller/Entities/SourceType.cs create mode 100644 MediaBrowser.Controller/Entities/Studio.cs create mode 100644 MediaBrowser.Controller/Entities/TV/Episode.cs create mode 100644 MediaBrowser.Controller/Entities/TV/Season.cs create mode 100644 MediaBrowser.Controller/Entities/TV/Series.cs create mode 100644 MediaBrowser.Controller/Entities/TagExtensions.cs create mode 100644 MediaBrowser.Controller/Entities/Trailer.cs create mode 100644 MediaBrowser.Controller/Entities/UserItemData.cs create mode 100644 MediaBrowser.Controller/Entities/UserRootFolder.cs create mode 100644 MediaBrowser.Controller/Entities/UserView.cs create mode 100644 MediaBrowser.Controller/Entities/UserViewBuilder.cs create mode 100644 MediaBrowser.Controller/Entities/Video.cs create mode 100644 MediaBrowser.Controller/Entities/Year.cs create mode 100644 MediaBrowser.Controller/Events/Authentication/AuthenticationRequestEventArgs.cs create mode 100644 MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs create mode 100644 MediaBrowser.Controller/Events/IEventConsumer.cs create mode 100644 MediaBrowser.Controller/Events/IEventManager.cs create mode 100644 MediaBrowser.Controller/Events/Session/SessionEndedEventArgs.cs create mode 100644 MediaBrowser.Controller/Events/Session/SessionStartedEventArgs.cs create mode 100644 MediaBrowser.Controller/Events/Updates/PluginInstallationCancelledEventArgs.cs create mode 100644 MediaBrowser.Controller/Events/Updates/PluginInstalledEventArgs.cs create mode 100644 MediaBrowser.Controller/Events/Updates/PluginInstallingEventArgs.cs create mode 100644 MediaBrowser.Controller/Events/Updates/PluginUninstalledEventArgs.cs create mode 100644 MediaBrowser.Controller/Events/Updates/PluginUpdatedEventArgs.cs create mode 100644 MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs create mode 100644 MediaBrowser.Controller/Extensions/XmlReaderExtensions.cs create mode 100644 MediaBrowser.Controller/IDisplayPreferencesManager.cs create mode 100644 MediaBrowser.Controller/IO/FileData.cs create mode 100644 MediaBrowser.Controller/IO/FileSystemHelper.cs create mode 100644 MediaBrowser.Controller/IO/IExternalDataManager.cs create mode 100644 MediaBrowser.Controller/IO/IPathManager.cs create mode 100644 MediaBrowser.Controller/IServerApplicationHost.cs create mode 100644 MediaBrowser.Controller/IServerApplicationPaths.cs create mode 100644 MediaBrowser.Controller/ISystemManager.cs create mode 100644 MediaBrowser.Controller/Library/DeleteOptions.cs create mode 100644 MediaBrowser.Controller/Library/IDirectStreamProvider.cs create mode 100644 MediaBrowser.Controller/Library/IIntroProvider.cs create mode 100644 MediaBrowser.Controller/Library/IKeyframeManager.cs create mode 100644 MediaBrowser.Controller/Library/ILibraryManager.cs create mode 100644 MediaBrowser.Controller/Library/ILibraryMonitor.cs create mode 100644 MediaBrowser.Controller/Library/ILibraryPostScanTask.cs create mode 100644 MediaBrowser.Controller/Library/ILiveStream.cs create mode 100644 MediaBrowser.Controller/Library/IMediaSourceManager.cs create mode 100644 MediaBrowser.Controller/Library/IMediaSourceProvider.cs create mode 100644 MediaBrowser.Controller/Library/IMetadataFileSaver.cs create mode 100644 MediaBrowser.Controller/Library/IMetadataSaver.cs create mode 100644 MediaBrowser.Controller/Library/IMusicManager.cs create mode 100644 MediaBrowser.Controller/Library/ISearchEngine.cs create mode 100644 MediaBrowser.Controller/Library/IUserDataManager.cs create mode 100644 MediaBrowser.Controller/Library/IUserManager.cs create mode 100644 MediaBrowser.Controller/Library/IUserViewManager.cs create mode 100644 MediaBrowser.Controller/Library/IntroInfo.cs create mode 100644 MediaBrowser.Controller/Library/ItemChangeEventArgs.cs create mode 100644 MediaBrowser.Controller/Library/ItemResolveArgs.cs create mode 100644 MediaBrowser.Controller/Library/ItemUpdateType.cs create mode 100644 MediaBrowser.Controller/Library/LibraryManagerExtensions.cs create mode 100644 MediaBrowser.Controller/Library/MetadataConfigurationExtensions.cs create mode 100644 MediaBrowser.Controller/Library/MetadataConfigurationStore.cs create mode 100644 MediaBrowser.Controller/Library/NameExtensions.cs create mode 100644 MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs create mode 100644 MediaBrowser.Controller/Library/PlaybackStartEventArgs.cs create mode 100644 MediaBrowser.Controller/Library/PlaybackStopEventArgs.cs create mode 100644 MediaBrowser.Controller/Library/SearchHintInfo.cs create mode 100644 MediaBrowser.Controller/Library/TVUtils.cs create mode 100644 MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs create mode 100644 MediaBrowser.Controller/LibraryTaskScheduler/ILimitedConcurrencyLibraryScheduler.cs create mode 100644 MediaBrowser.Controller/LibraryTaskScheduler/LimitedConcurrencyLibraryScheduler.cs create mode 100644 MediaBrowser.Controller/LiveTv/ActiveRecordingInfo.cs create mode 100644 MediaBrowser.Controller/LiveTv/ChannelInfo.cs create mode 100644 MediaBrowser.Controller/LiveTv/IGuideManager.cs create mode 100644 MediaBrowser.Controller/LiveTv/IListingsManager.cs create mode 100644 MediaBrowser.Controller/LiveTv/IListingsProvider.cs create mode 100644 MediaBrowser.Controller/LiveTv/ILiveTvManager.cs create mode 100644 MediaBrowser.Controller/LiveTv/ILiveTvService.cs create mode 100644 MediaBrowser.Controller/LiveTv/IRecordingsManager.cs create mode 100644 MediaBrowser.Controller/LiveTv/ITunerHost.cs create mode 100644 MediaBrowser.Controller/LiveTv/ITunerHostManager.cs create mode 100644 MediaBrowser.Controller/LiveTv/LiveTvChannel.cs create mode 100644 MediaBrowser.Controller/LiveTv/LiveTvConflictException.cs create mode 100644 MediaBrowser.Controller/LiveTv/LiveTvProgram.cs create mode 100644 MediaBrowser.Controller/LiveTv/ProgramInfo.cs create mode 100644 MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs create mode 100644 MediaBrowser.Controller/LiveTv/TimerEventInfo.cs create mode 100644 MediaBrowser.Controller/LiveTv/TimerInfo.cs create mode 100644 MediaBrowser.Controller/Lyrics/ILyricManager.cs create mode 100644 MediaBrowser.Controller/Lyrics/ILyricParser.cs create mode 100644 MediaBrowser.Controller/Lyrics/ILyricProvider.cs create mode 100644 MediaBrowser.Controller/Lyrics/LyricDownloadFailureEventArgs.cs create mode 100644 MediaBrowser.Controller/MediaBrowser.Controller.csproj create mode 100644 MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs create mode 100644 MediaBrowser.Controller/MediaEncoding/BitStreamFilterOptionType.cs create mode 100644 MediaBrowser.Controller/MediaEncoding/DownMixAlgorithmsHelper.cs create mode 100644 MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs create mode 100644 MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs create mode 100644 MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs create mode 100644 MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs create mode 100644 MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs create mode 100644 MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs create mode 100644 MediaBrowser.Controller/MediaEncoding/ITranscodeManager.cs create mode 100644 MediaBrowser.Controller/MediaEncoding/JobLogger.cs create mode 100644 MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs create mode 100644 MediaBrowser.Controller/MediaEncoding/TranscodingJob.cs create mode 100644 MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs create mode 100644 MediaBrowser.Controller/MediaEncoding/TranscodingSegmentCleaner.cs create mode 100644 MediaBrowser.Controller/MediaEncoding/TranscodingThrottler.cs create mode 100644 MediaBrowser.Controller/MediaSegments/IMediaSegmentManager.cs create mode 100644 MediaBrowser.Controller/MediaSegments/IMediaSegmentProvider.cs create mode 100644 MediaBrowser.Controller/Net/AuthorizationInfo.cs create mode 100644 MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs create mode 100644 MediaBrowser.Controller/Net/IAuthService.cs create mode 100644 MediaBrowser.Controller/Net/IAuthorizationContext.cs create mode 100644 MediaBrowser.Controller/Net/IWebSocketConnection.cs create mode 100644 MediaBrowser.Controller/Net/IWebSocketListener.cs create mode 100644 MediaBrowser.Controller/Net/IWebSocketManager.cs create mode 100644 MediaBrowser.Controller/Net/SecurityException.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketListenerState.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessageInfo.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessageOfT.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/IInboundWebSocketMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/IOutboundWebSocketMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStartMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStopMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Inbound/InboundKeepAliveMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStartMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStopMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStartMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStopMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/InboundWebSocketMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/InboundWebSocketMessageOfT.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ActivityLogEntryMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ForceKeepAliveMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/GeneralCommandMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/LibraryChangedMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/OutboundKeepAliveMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlayMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlaystateMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCancelledMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCompletedMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationFailedMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallingMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginUninstalledMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RefreshProgressMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RestartRequiredMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTaskEndedMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTasksInfoMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCancelledMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCreatedMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerRestartingMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerShuttingDownMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayCommandMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCancelledMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCreatedMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDataChangedMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDeletedMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserUpdatedMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/OutboundWebSocketMessage.cs create mode 100644 MediaBrowser.Controller/Net/WebSocketMessages/OutboundWebSocketMessageOfT.cs create mode 100644 MediaBrowser.Controller/Persistence/IChapterRepository.cs create mode 100644 MediaBrowser.Controller/Persistence/IItemRepository.cs create mode 100644 MediaBrowser.Controller/Persistence/IItemTypeLookup.cs create mode 100644 MediaBrowser.Controller/Persistence/IKeyframeRepository.cs create mode 100644 MediaBrowser.Controller/Persistence/IMediaAttachmentRepository.cs create mode 100644 MediaBrowser.Controller/Persistence/IMediaStreamRepository.cs create mode 100644 MediaBrowser.Controller/Persistence/IPeopleRepository.cs create mode 100644 MediaBrowser.Controller/Persistence/MediaAttachmentQuery.cs create mode 100644 MediaBrowser.Controller/Persistence/MediaStreamQuery.cs create mode 100644 MediaBrowser.Controller/Playlists/IPlaylistManager.cs create mode 100644 MediaBrowser.Controller/Playlists/Playlist.cs create mode 100644 MediaBrowser.Controller/Plugins/IPluginServiceRegistrator.cs create mode 100644 MediaBrowser.Controller/Properties/AssemblyInfo.cs create mode 100644 MediaBrowser.Controller/Providers/AlbumInfo.cs create mode 100644 MediaBrowser.Controller/Providers/ArtistInfo.cs create mode 100644 MediaBrowser.Controller/Providers/BookInfo.cs create mode 100644 MediaBrowser.Controller/Providers/BoxSetInfo.cs create mode 100644 MediaBrowser.Controller/Providers/DirectoryService.cs create mode 100644 MediaBrowser.Controller/Providers/DynamicImageResponse.cs create mode 100644 MediaBrowser.Controller/Providers/EpisodeInfo.cs create mode 100644 MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs create mode 100644 MediaBrowser.Controller/Providers/IDirectoryService.cs create mode 100644 MediaBrowser.Controller/Providers/IDynamicImageProvider.cs create mode 100644 MediaBrowser.Controller/Providers/IExternalId.cs create mode 100644 MediaBrowser.Controller/Providers/IExternalUrlProvider.cs create mode 100644 MediaBrowser.Controller/Providers/IForcedProvider.cs create mode 100644 MediaBrowser.Controller/Providers/IHasItemChangeMonitor.cs create mode 100644 MediaBrowser.Controller/Providers/IHasLookupInfo.cs create mode 100644 MediaBrowser.Controller/Providers/IHasOrder.cs create mode 100644 MediaBrowser.Controller/Providers/IImageProvider.cs create mode 100644 MediaBrowser.Controller/Providers/ILocalImageProvider.cs create mode 100644 MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs create mode 100644 MediaBrowser.Controller/Providers/IMetadataProvider.cs create mode 100644 MediaBrowser.Controller/Providers/IMetadataService.cs create mode 100644 MediaBrowser.Controller/Providers/IPreRefreshProvider.cs create mode 100644 MediaBrowser.Controller/Providers/IProviderManager.cs create mode 100644 MediaBrowser.Controller/Providers/IRemoteImageProvider.cs create mode 100644 MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs create mode 100644 MediaBrowser.Controller/Providers/IRemoteSearchProvider.cs create mode 100644 MediaBrowser.Controller/Providers/ImageRefreshOptions.cs create mode 100644 MediaBrowser.Controller/Providers/ItemInfo.cs create mode 100644 MediaBrowser.Controller/Providers/ItemLookupInfo.cs create mode 100644 MediaBrowser.Controller/Providers/LocalImageInfo.cs create mode 100644 MediaBrowser.Controller/Providers/MetadataRefreshMode.cs create mode 100644 MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs create mode 100644 MediaBrowser.Controller/Providers/MetadataResult.cs create mode 100644 MediaBrowser.Controller/Providers/MovieInfo.cs create mode 100644 MediaBrowser.Controller/Providers/MusicVideoInfo.cs create mode 100644 MediaBrowser.Controller/Providers/PersonLookupInfo.cs create mode 100644 MediaBrowser.Controller/Providers/RefreshPriority.cs create mode 100644 MediaBrowser.Controller/Providers/RemoteSearchQuery.cs create mode 100644 MediaBrowser.Controller/Providers/SeasonInfo.cs create mode 100644 MediaBrowser.Controller/Providers/SeriesInfo.cs create mode 100644 MediaBrowser.Controller/Providers/SongInfo.cs create mode 100644 MediaBrowser.Controller/Providers/TrailerInfo.cs create mode 100644 MediaBrowser.Controller/Providers/VideoContentType.cs create mode 100644 MediaBrowser.Controller/QuickConnect/IQuickConnect.cs create mode 100644 MediaBrowser.Controller/Resolvers/IItemResolver.cs create mode 100644 MediaBrowser.Controller/Resolvers/IResolverIgnoreRule.cs create mode 100644 MediaBrowser.Controller/Resolvers/ItemResolver.cs create mode 100644 MediaBrowser.Controller/Resolvers/ResolverPriority.cs create mode 100644 MediaBrowser.Controller/Security/AuthenticationInfo.cs create mode 100644 MediaBrowser.Controller/Security/IAuthenticationManager.cs create mode 100644 MediaBrowser.Controller/Session/AuthenticationRequest.cs create mode 100644 MediaBrowser.Controller/Session/ISessionController.cs create mode 100644 MediaBrowser.Controller/Session/ISessionManager.cs create mode 100644 MediaBrowser.Controller/Session/SessionEventArgs.cs create mode 100644 MediaBrowser.Controller/Session/SessionInfo.cs create mode 100644 MediaBrowser.Controller/Sorting/IBaseItemComparer.cs create mode 100644 MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs create mode 100644 MediaBrowser.Controller/Sorting/SortExtensions.cs create mode 100644 MediaBrowser.Controller/Streaming/ProgressiveFileStream.cs create mode 100644 MediaBrowser.Controller/Streaming/StreamState.cs create mode 100644 MediaBrowser.Controller/Streaming/StreamingRequestDto.cs create mode 100644 MediaBrowser.Controller/Streaming/VideoRequestDto.cs create mode 100644 MediaBrowser.Controller/Subtitles/ISubtitleManager.cs create mode 100644 MediaBrowser.Controller/Subtitles/ISubtitleProvider.cs create mode 100644 MediaBrowser.Controller/Subtitles/SubtitleDownloadFailureEventArgs.cs create mode 100644 MediaBrowser.Controller/Subtitles/SubtitleResponse.cs create mode 100644 MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/GroupMember.cs create mode 100644 MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs create mode 100644 MediaBrowser.Controller/SyncPlay/GroupStates/IdleGroupState.cs create mode 100644 MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs create mode 100644 MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs create mode 100644 MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs create mode 100644 MediaBrowser.Controller/SyncPlay/IGroupPlaybackRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/IGroupState.cs create mode 100644 MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs create mode 100644 MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs create mode 100644 MediaBrowser.Controller/SyncPlay/ISyncPlayRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequests/AbstractPlaybackRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequests/BufferGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequests/IgnoreWaitGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequests/MovePlaylistItemGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequests/NextItemGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequests/PauseGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequests/PingGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequests/PlayGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequests/PreviousItemGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequests/QueueGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequests/ReadyGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequests/SeekGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetPlaylistItemGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetRepeatModeGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetShuffleModeGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequests/StopGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequests/UnpauseGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs create mode 100644 MediaBrowser.Controller/SyncPlay/Requests/JoinGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/Requests/LeaveGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/Requests/ListGroupsRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/Requests/NewGroupRequest.cs create mode 100644 MediaBrowser.Controller/SystemBackupService/BackupManifestDto.cs create mode 100644 MediaBrowser.Controller/SystemBackupService/BackupOptionsDto.cs create mode 100644 MediaBrowser.Controller/SystemBackupService/BackupRestoreRequestDto.cs create mode 100644 MediaBrowser.Controller/SystemBackupService/IBackupService.cs create mode 100644 MediaBrowser.Controller/TV/ITVSeriesManager.cs create mode 100644 MediaBrowser.Controller/Trickplay/ITrickplayManager.cs create mode 100644 MediaBrowser.Controller/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 MediaBrowser.Controller/obj/Debug/net10.0/MediaBrowser.Controller.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 MediaBrowser.Controller/obj/Debug/net10.0/MediaBrowser.Controller.assets.cache create mode 100644 MediaBrowser.Controller/obj/Debug/net10.0/MediaBrowser.Controller.csproj.AssemblyReference.cache create mode 100644 MediaBrowser.Controller/obj/MediaBrowser.Controller.csproj.nuget.dgspec.json create mode 100644 MediaBrowser.Controller/obj/MediaBrowser.Controller.csproj.nuget.g.props create mode 100644 MediaBrowser.Controller/obj/MediaBrowser.Controller.csproj.nuget.g.targets create mode 100644 MediaBrowser.Controller/obj/project.assets.json create mode 100644 MediaBrowser.Controller/obj/project.nuget.cache create mode 100644 MediaBrowser.Controller/obj/project.packagespec.json create mode 100644 MediaBrowser.Controller/obj/rider.project.model.nuget.info create mode 100644 MediaBrowser.Controller/obj/rider.project.restore.info create mode 100644 MediaBrowser.LocalMetadata/BaseXmlProvider.cs create mode 100644 MediaBrowser.LocalMetadata/Images/CollectionFolderLocalImageProvider.cs create mode 100644 MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs create mode 100644 MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs create mode 100644 MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs create mode 100644 MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj create mode 100644 MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs create mode 100644 MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs create mode 100644 MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs create mode 100644 MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs create mode 100644 MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs create mode 100644 MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs create mode 100644 MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs create mode 100644 MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs create mode 100644 MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs create mode 100644 MediaBrowser.LocalMetadata/XmlProviderUtils.cs create mode 100644 MediaBrowser.LocalMetadata/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 MediaBrowser.LocalMetadata/obj/Debug/net10.0/MediaBrowser.LocalMetadata.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 MediaBrowser.LocalMetadata/obj/Debug/net10.0/MediaBrowser.LocalMetadata.assets.cache create mode 100644 MediaBrowser.LocalMetadata/obj/Debug/net10.0/MediaBrowser.LocalMetadata.csproj.AssemblyReference.cache create mode 100644 MediaBrowser.LocalMetadata/obj/MediaBrowser.LocalMetadata.csproj.nuget.dgspec.json create mode 100644 MediaBrowser.LocalMetadata/obj/MediaBrowser.LocalMetadata.csproj.nuget.g.props create mode 100644 MediaBrowser.LocalMetadata/obj/MediaBrowser.LocalMetadata.csproj.nuget.g.targets create mode 100644 MediaBrowser.LocalMetadata/obj/project.assets.json create mode 100644 MediaBrowser.LocalMetadata/obj/project.nuget.cache create mode 100644 MediaBrowser.LocalMetadata/obj/project.packagespec.json create mode 100644 MediaBrowser.LocalMetadata/obj/rider.project.model.nuget.info create mode 100644 MediaBrowser.LocalMetadata/obj/rider.project.restore.info create mode 100644 MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs create mode 100644 MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs create mode 100644 MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs create mode 100644 MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs create mode 100644 MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs create mode 100644 MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationStore.cs create mode 100644 MediaBrowser.MediaEncoding/Encoder/ApplePlatformHelper.cs create mode 100644 MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs create mode 100644 MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs create mode 100644 MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs create mode 100644 MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj create mode 100644 MediaBrowser.MediaEncoding/Probing/CodecType.cs create mode 100644 MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs create mode 100644 MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs create mode 100644 MediaBrowser.MediaEncoding/Probing/MediaChapter.cs create mode 100644 MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs create mode 100644 MediaBrowser.MediaEncoding/Probing/MediaFrameInfo.cs create mode 100644 MediaBrowser.MediaEncoding/Probing/MediaFrameSideDataInfo.cs create mode 100644 MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs create mode 100644 MediaBrowser.MediaEncoding/Probing/MediaStreamInfoSideData.cs create mode 100644 MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs create mode 100644 MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs create mode 100644 MediaBrowser.MediaEncoding/Subtitles/AssWriter.cs create mode 100644 MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs create mode 100644 MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs create mode 100644 MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs create mode 100644 MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs create mode 100644 MediaBrowser.MediaEncoding/Subtitles/SsaWriter.cs create mode 100644 MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs create mode 100644 MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs create mode 100644 MediaBrowser.MediaEncoding/Subtitles/SubtitleFormatExtensions.cs create mode 100644 MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs create mode 100644 MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs create mode 100644 MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs create mode 100644 MediaBrowser.MediaEncoding/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 MediaBrowser.MediaEncoding/obj/Debug/net10.0/MediaBrowser.MediaEncoding.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 MediaBrowser.MediaEncoding/obj/Debug/net10.0/MediaBrowser.MediaEncoding.assets.cache create mode 100644 MediaBrowser.MediaEncoding/obj/Debug/net10.0/MediaBrowser.MediaEncoding.csproj.AssemblyReference.cache create mode 100644 MediaBrowser.MediaEncoding/obj/MediaBrowser.MediaEncoding.csproj.nuget.dgspec.json create mode 100644 MediaBrowser.MediaEncoding/obj/MediaBrowser.MediaEncoding.csproj.nuget.g.props create mode 100644 MediaBrowser.MediaEncoding/obj/MediaBrowser.MediaEncoding.csproj.nuget.g.targets create mode 100644 MediaBrowser.MediaEncoding/obj/project.assets.json create mode 100644 MediaBrowser.MediaEncoding/obj/project.nuget.cache create mode 100644 MediaBrowser.MediaEncoding/obj/project.packagespec.json create mode 100644 MediaBrowser.MediaEncoding/obj/rider.project.model.nuget.info create mode 100644 MediaBrowser.MediaEncoding/obj/rider.project.restore.info create mode 100644 MediaBrowser.Model/Activity/ActivityLogEntry.cs create mode 100644 MediaBrowser.Model/Activity/IActivityManager.cs create mode 100644 MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs create mode 100644 MediaBrowser.Model/Branding/BrandingOptions.cs create mode 100644 MediaBrowser.Model/Branding/BrandingOptionsDto.cs create mode 100644 MediaBrowser.Model/Channels/ChannelFeatures.cs create mode 100644 MediaBrowser.Model/Channels/ChannelFolderType.cs create mode 100644 MediaBrowser.Model/Channels/ChannelItemSortField.cs create mode 100644 MediaBrowser.Model/Channels/ChannelMediaContentType.cs create mode 100644 MediaBrowser.Model/Channels/ChannelMediaType.cs create mode 100644 MediaBrowser.Model/Channels/ChannelQuery.cs create mode 100644 MediaBrowser.Model/Collections/CollectionCreationResult.cs create mode 100644 MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs create mode 100644 MediaBrowser.Model/Configuration/EmbeddedSubtitleOptions.cs create mode 100644 MediaBrowser.Model/Configuration/EncodingOptions.cs create mode 100644 MediaBrowser.Model/Configuration/HlsAudioSeekStrategy.cs create mode 100644 MediaBrowser.Model/Configuration/ImageOption.cs create mode 100644 MediaBrowser.Model/Configuration/ImageSavingConvention.cs create mode 100644 MediaBrowser.Model/Configuration/LibraryOptions.cs create mode 100644 MediaBrowser.Model/Configuration/MediaPathInfo.cs create mode 100644 MediaBrowser.Model/Configuration/MetadataConfiguration.cs create mode 100644 MediaBrowser.Model/Configuration/MetadataOptions.cs create mode 100644 MediaBrowser.Model/Configuration/MetadataPlugin.cs create mode 100644 MediaBrowser.Model/Configuration/MetadataPluginSummary.cs create mode 100644 MediaBrowser.Model/Configuration/MetadataPluginType.cs create mode 100644 MediaBrowser.Model/Configuration/PathSubstitution.cs create mode 100644 MediaBrowser.Model/Configuration/ServerConfiguration.cs create mode 100644 MediaBrowser.Model/Configuration/TrickplayOptions.cs create mode 100644 MediaBrowser.Model/Configuration/TrickplayScanBehavior.cs create mode 100644 MediaBrowser.Model/Configuration/TypeOptions.cs create mode 100644 MediaBrowser.Model/Configuration/UserConfiguration.cs create mode 100644 MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs create mode 100644 MediaBrowser.Model/Cryptography/Constants.cs create mode 100644 MediaBrowser.Model/Cryptography/ICryptoProvider.cs create mode 100644 MediaBrowser.Model/Cryptography/PasswordHash.cs create mode 100644 MediaBrowser.Model/Devices/DeviceInfo.cs create mode 100644 MediaBrowser.Model/Dlna/CodecProfile.cs create mode 100644 MediaBrowser.Model/Dlna/CodecType.cs create mode 100644 MediaBrowser.Model/Dlna/ConditionProcessor.cs create mode 100644 MediaBrowser.Model/Dlna/ContainerProfile.cs create mode 100644 MediaBrowser.Model/Dlna/DeviceProfile.cs create mode 100644 MediaBrowser.Model/Dlna/DirectPlayProfile.cs create mode 100644 MediaBrowser.Model/Dlna/DlnaProfileType.cs create mode 100644 MediaBrowser.Model/Dlna/EncodingContext.cs create mode 100644 MediaBrowser.Model/Dlna/ITranscoderSupport.cs create mode 100644 MediaBrowser.Model/Dlna/MediaOptions.cs create mode 100644 MediaBrowser.Model/Dlna/PlaybackErrorCode.cs create mode 100644 MediaBrowser.Model/Dlna/ProfileCondition.cs create mode 100644 MediaBrowser.Model/Dlna/ProfileConditionType.cs create mode 100644 MediaBrowser.Model/Dlna/ProfileConditionValue.cs create mode 100644 MediaBrowser.Model/Dlna/ResolutionConfiguration.cs create mode 100644 MediaBrowser.Model/Dlna/ResolutionNormalizer.cs create mode 100644 MediaBrowser.Model/Dlna/ResolutionOptions.cs create mode 100644 MediaBrowser.Model/Dlna/StreamBuilder.cs create mode 100644 MediaBrowser.Model/Dlna/StreamInfo.cs create mode 100644 MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs create mode 100644 MediaBrowser.Model/Dlna/SubtitleProfile.cs create mode 100644 MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs create mode 100644 MediaBrowser.Model/Dlna/TranscodeSeekInfo.cs create mode 100644 MediaBrowser.Model/Dlna/TranscodingProfile.cs create mode 100644 MediaBrowser.Model/Drawing/DrawingUtils.cs create mode 100644 MediaBrowser.Model/Drawing/ImageDimensions.cs create mode 100644 MediaBrowser.Model/Drawing/ImageFormat.cs create mode 100644 MediaBrowser.Model/Drawing/ImageFormatExtensions.cs create mode 100644 MediaBrowser.Model/Drawing/ImageOrientation.cs create mode 100644 MediaBrowser.Model/Drawing/ImageResolution.cs create mode 100644 MediaBrowser.Model/Dto/BaseItemDto.cs create mode 100644 MediaBrowser.Model/Dto/BaseItemPerson.cs create mode 100644 MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs create mode 100644 MediaBrowser.Model/Dto/DeviceInfoDto.cs create mode 100644 MediaBrowser.Model/Dto/DisplayPreferencesDto.cs create mode 100644 MediaBrowser.Model/Dto/IHasServerId.cs create mode 100644 MediaBrowser.Model/Dto/IItemDto.cs create mode 100644 MediaBrowser.Model/Dto/ImageInfo.cs create mode 100644 MediaBrowser.Model/Dto/ItemCounts.cs create mode 100644 MediaBrowser.Model/Dto/MediaSourceInfo.cs create mode 100644 MediaBrowser.Model/Dto/MediaSourceType.cs create mode 100644 MediaBrowser.Model/Dto/MetadataEditorInfo.cs create mode 100644 MediaBrowser.Model/Dto/NameGuidPair.cs create mode 100644 MediaBrowser.Model/Dto/NameIdPair.cs create mode 100644 MediaBrowser.Model/Dto/NameValuePair.cs create mode 100644 MediaBrowser.Model/Dto/PlaylistDto.cs create mode 100644 MediaBrowser.Model/Dto/RatingType.cs create mode 100644 MediaBrowser.Model/Dto/RecommendationDto.cs create mode 100644 MediaBrowser.Model/Dto/RecommendationType.cs create mode 100644 MediaBrowser.Model/Dto/SessionInfoDto.cs create mode 100644 MediaBrowser.Model/Dto/TrickplayInfoDto.cs create mode 100644 MediaBrowser.Model/Dto/UpdateUserItemDataDto.cs create mode 100644 MediaBrowser.Model/Dto/UserDto.cs create mode 100644 MediaBrowser.Model/Dto/UserItemDataDto.cs create mode 100644 MediaBrowser.Model/Entities/ChapterInfo.cs create mode 100644 MediaBrowser.Model/Entities/CollectionTypeOptions.cs create mode 100644 MediaBrowser.Model/Entities/DeinterlaceMethod.cs create mode 100644 MediaBrowser.Model/Entities/DownMixStereoAlgorithms.cs create mode 100644 MediaBrowser.Model/Entities/EncoderPreset.cs create mode 100644 MediaBrowser.Model/Entities/ExtraType.cs create mode 100644 MediaBrowser.Model/Entities/HardwareAccelerationType.cs create mode 100644 MediaBrowser.Model/Entities/IHasProviderIds.cs create mode 100644 MediaBrowser.Model/Entities/IHasShares.cs create mode 100644 MediaBrowser.Model/Entities/ImageType.cs create mode 100644 MediaBrowser.Model/Entities/IsoType.cs create mode 100644 MediaBrowser.Model/Entities/LibraryUpdateInfo.cs create mode 100644 MediaBrowser.Model/Entities/LocationType.cs create mode 100644 MediaBrowser.Model/Entities/MediaAttachment.cs create mode 100644 MediaBrowser.Model/Entities/MediaStream.cs create mode 100644 MediaBrowser.Model/Entities/MediaStreamType.cs create mode 100644 MediaBrowser.Model/Entities/MediaUrl.cs create mode 100644 MediaBrowser.Model/Entities/MetadataField.cs create mode 100644 MediaBrowser.Model/Entities/MetadataProvider.cs create mode 100644 MediaBrowser.Model/Entities/ParentalRating.cs create mode 100644 MediaBrowser.Model/Entities/ParentalRatingEntry.cs create mode 100644 MediaBrowser.Model/Entities/ParentalRatingScore.cs create mode 100644 MediaBrowser.Model/Entities/ParentalRatingSystem.cs create mode 100644 MediaBrowser.Model/Entities/PersonType.cs create mode 100644 MediaBrowser.Model/Entities/PlaylistUserPermissions.cs create mode 100644 MediaBrowser.Model/Entities/ProviderIdsExtensions.cs create mode 100644 MediaBrowser.Model/Entities/SeriesStatus.cs create mode 100644 MediaBrowser.Model/Entities/TonemappingAlgorithm.cs create mode 100644 MediaBrowser.Model/Entities/TonemappingMode.cs create mode 100644 MediaBrowser.Model/Entities/TonemappingRange.cs create mode 100644 MediaBrowser.Model/Entities/TrailerType.cs create mode 100644 MediaBrowser.Model/Entities/UserDataSaveReason.cs create mode 100644 MediaBrowser.Model/Entities/Video3DFormat.cs create mode 100644 MediaBrowser.Model/Entities/VideoType.cs create mode 100644 MediaBrowser.Model/Entities/VirtualFolderInfo.cs create mode 100644 MediaBrowser.Model/Extensions/ContainerHelper.cs create mode 100644 MediaBrowser.Model/Extensions/EnumerableExtensions.cs create mode 100644 MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs create mode 100644 MediaBrowser.Model/Extensions/StringHelper.cs create mode 100644 MediaBrowser.Model/Globalization/CountryInfo.cs create mode 100644 MediaBrowser.Model/Globalization/CultureDto.cs create mode 100644 MediaBrowser.Model/Globalization/ILocalizationManager.cs create mode 100644 MediaBrowser.Model/Globalization/LocalizationOption.cs create mode 100644 MediaBrowser.Model/IO/AsyncFile.cs create mode 100644 MediaBrowser.Model/IO/FileSystemEntryInfo.cs create mode 100644 MediaBrowser.Model/IO/FileSystemEntryType.cs create mode 100644 MediaBrowser.Model/IO/FileSystemMetadata.cs create mode 100644 MediaBrowser.Model/IO/IFileSystem.cs create mode 100644 MediaBrowser.Model/IO/IODefaults.cs create mode 100644 MediaBrowser.Model/IO/IShortcutHandler.cs create mode 100644 MediaBrowser.Model/IO/IStreamHelper.cs create mode 100644 MediaBrowser.Model/Library/PlayAccess.cs create mode 100644 MediaBrowser.Model/Library/UserViewQuery.cs create mode 100644 MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs create mode 100644 MediaBrowser.Model/LiveTv/ChannelMappingOptionsDto.cs create mode 100644 MediaBrowser.Model/LiveTv/ChannelType.cs create mode 100644 MediaBrowser.Model/LiveTv/DayPattern.cs create mode 100644 MediaBrowser.Model/LiveTv/GuideInfo.cs create mode 100644 MediaBrowser.Model/LiveTv/ListingsProviderInfo.cs create mode 100644 MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs create mode 100644 MediaBrowser.Model/LiveTv/LiveTvInfo.cs create mode 100644 MediaBrowser.Model/LiveTv/LiveTvOptions.cs create mode 100644 MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs create mode 100644 MediaBrowser.Model/LiveTv/LiveTvServiceStatus.cs create mode 100644 MediaBrowser.Model/LiveTv/ProgramAudio.cs create mode 100644 MediaBrowser.Model/LiveTv/RecordingQuery.cs create mode 100644 MediaBrowser.Model/LiveTv/RecordingStatus.cs create mode 100644 MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs create mode 100644 MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs create mode 100644 MediaBrowser.Model/LiveTv/TimerInfoDto.cs create mode 100644 MediaBrowser.Model/LiveTv/TimerQuery.cs create mode 100644 MediaBrowser.Model/LiveTv/TunerChannelMapping.cs create mode 100644 MediaBrowser.Model/LiveTv/TunerHostInfo.cs create mode 100644 MediaBrowser.Model/Lyrics/LyricDto.cs create mode 100644 MediaBrowser.Model/Lyrics/LyricFile.cs create mode 100644 MediaBrowser.Model/Lyrics/LyricLine.cs create mode 100644 MediaBrowser.Model/Lyrics/LyricLineCue.cs create mode 100644 MediaBrowser.Model/Lyrics/LyricMetadata.cs create mode 100644 MediaBrowser.Model/Lyrics/LyricResponse.cs create mode 100644 MediaBrowser.Model/Lyrics/LyricSearchRequest.cs create mode 100644 MediaBrowser.Model/Lyrics/RemoteLyricInfoDto.cs create mode 100644 MediaBrowser.Model/Lyrics/UploadLyricDto.cs create mode 100644 MediaBrowser.Model/MediaBrowser.Model.csproj create mode 100644 MediaBrowser.Model/MediaInfo/AudioCodec.cs create mode 100644 MediaBrowser.Model/MediaInfo/AudioIndexSource.cs create mode 100644 MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs create mode 100644 MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs create mode 100644 MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs create mode 100644 MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs create mode 100644 MediaBrowser.Model/MediaInfo/MediaInfo.cs create mode 100644 MediaBrowser.Model/MediaInfo/MediaProtocol.cs create mode 100644 MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs create mode 100644 MediaBrowser.Model/MediaInfo/SubtitleFormat.cs create mode 100644 MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs create mode 100644 MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs create mode 100644 MediaBrowser.Model/MediaInfo/TransportStreamTimestamp.cs create mode 100644 MediaBrowser.Model/MediaSegments/MediaSegmentDto.cs create mode 100644 MediaBrowser.Model/MediaSegments/MediaSegmentGenerationRequest.cs create mode 100644 MediaBrowser.Model/Net/EndPointInfo.cs create mode 100644 MediaBrowser.Model/Net/IPData.cs create mode 100644 MediaBrowser.Model/Net/ISocketFactory.cs create mode 100644 MediaBrowser.Model/Net/MimeTypes.cs create mode 100644 MediaBrowser.Model/Net/PublishedServerUriOverride.cs create mode 100644 MediaBrowser.Model/Notifications/NotificationType.cs create mode 100644 MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs create mode 100644 MediaBrowser.Model/Playlists/PlaylistCreationResult.cs create mode 100644 MediaBrowser.Model/Playlists/PlaylistUpdateRequest.cs create mode 100644 MediaBrowser.Model/Playlists/PlaylistUserUpdateRequest.cs create mode 100644 MediaBrowser.Model/Plugins/BasePluginConfiguration.cs create mode 100644 MediaBrowser.Model/Plugins/IHasWebPages.cs create mode 100644 MediaBrowser.Model/Plugins/PluginInfo.cs create mode 100644 MediaBrowser.Model/Plugins/PluginPageInfo.cs create mode 100644 MediaBrowser.Model/Plugins/PluginStatus.cs create mode 100644 MediaBrowser.Model/Properties/AssemblyInfo.cs create mode 100644 MediaBrowser.Model/Providers/ExternalIdInfo.cs create mode 100644 MediaBrowser.Model/Providers/ExternalIdMediaType.cs create mode 100644 MediaBrowser.Model/Providers/ExternalUrl.cs create mode 100644 MediaBrowser.Model/Providers/ImageProviderInfo.cs create mode 100644 MediaBrowser.Model/Providers/LyricProviderInfo.cs create mode 100644 MediaBrowser.Model/Providers/RemoteImageInfo.cs create mode 100644 MediaBrowser.Model/Providers/RemoteImageQuery.cs create mode 100644 MediaBrowser.Model/Providers/RemoteImageResult.cs create mode 100644 MediaBrowser.Model/Providers/RemoteLyricInfo.cs create mode 100644 MediaBrowser.Model/Providers/RemoteSearchResult.cs create mode 100644 MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs create mode 100644 MediaBrowser.Model/Providers/SubtitleOptions.cs create mode 100644 MediaBrowser.Model/Providers/SubtitleProviderInfo.cs create mode 100644 MediaBrowser.Model/Querying/AllThemeMediaResult.cs create mode 100644 MediaBrowser.Model/Querying/ItemFields.cs create mode 100644 MediaBrowser.Model/Querying/ItemFilter.cs create mode 100644 MediaBrowser.Model/Querying/LatestItemsQuery.cs create mode 100644 MediaBrowser.Model/Querying/NextUpQuery.cs create mode 100644 MediaBrowser.Model/Querying/QueryFilters.cs create mode 100644 MediaBrowser.Model/Querying/QueryFiltersLegacy.cs create mode 100644 MediaBrowser.Model/Querying/QueryResult.cs create mode 100644 MediaBrowser.Model/Querying/ThemeMediaResult.cs create mode 100644 MediaBrowser.Model/QuickConnect/QuickConnectResult.cs create mode 100644 MediaBrowser.Model/Search/SearchHint.cs create mode 100644 MediaBrowser.Model/Search/SearchHintResult.cs create mode 100644 MediaBrowser.Model/Search/SearchQuery.cs create mode 100644 MediaBrowser.Model/Serialization/IXmlSerializer.cs create mode 100644 MediaBrowser.Model/Session/BrowseRequest.cs create mode 100644 MediaBrowser.Model/Session/ClientCapabilities.cs create mode 100644 MediaBrowser.Model/Session/GeneralCommand.cs create mode 100644 MediaBrowser.Model/Session/GeneralCommandType.cs create mode 100644 MediaBrowser.Model/Session/MessageCommand.cs create mode 100644 MediaBrowser.Model/Session/PlayCommand.cs create mode 100644 MediaBrowser.Model/Session/PlayMethod.cs create mode 100644 MediaBrowser.Model/Session/PlayRequest.cs create mode 100644 MediaBrowser.Model/Session/PlaybackOrder.cs create mode 100644 MediaBrowser.Model/Session/PlaybackProgressInfo.cs create mode 100644 MediaBrowser.Model/Session/PlaybackStartInfo.cs create mode 100644 MediaBrowser.Model/Session/PlaybackStopInfo.cs create mode 100644 MediaBrowser.Model/Session/PlayerStateInfo.cs create mode 100644 MediaBrowser.Model/Session/PlaystateCommand.cs create mode 100644 MediaBrowser.Model/Session/PlaystateRequest.cs create mode 100644 MediaBrowser.Model/Session/QueueItem.cs create mode 100644 MediaBrowser.Model/Session/RepeatMode.cs create mode 100644 MediaBrowser.Model/Session/SessionMessageType.cs create mode 100644 MediaBrowser.Model/Session/SessionUserInfo.cs create mode 100644 MediaBrowser.Model/Session/TranscodeReason.cs create mode 100644 MediaBrowser.Model/Session/TranscodingInfo.cs create mode 100644 MediaBrowser.Model/Session/UserDataChangeInfo.cs create mode 100644 MediaBrowser.Model/Subtitles/FontFile.cs create mode 100644 MediaBrowser.Model/SyncPlay/GroupInfoDto.cs create mode 100644 MediaBrowser.Model/SyncPlay/GroupQueueMode.cs create mode 100644 MediaBrowser.Model/SyncPlay/GroupRepeatMode.cs create mode 100644 MediaBrowser.Model/SyncPlay/GroupShuffleMode.cs create mode 100644 MediaBrowser.Model/SyncPlay/GroupStateType.cs create mode 100644 MediaBrowser.Model/SyncPlay/GroupStateUpdate.cs create mode 100644 MediaBrowser.Model/SyncPlay/GroupUpdate.cs create mode 100644 MediaBrowser.Model/SyncPlay/GroupUpdateType.cs create mode 100644 MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs create mode 100644 MediaBrowser.Model/SyncPlay/PlayQueueUpdateReason.cs create mode 100644 MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs create mode 100644 MediaBrowser.Model/SyncPlay/RequestType.cs create mode 100644 MediaBrowser.Model/SyncPlay/SendCommand.cs create mode 100644 MediaBrowser.Model/SyncPlay/SendCommandType.cs create mode 100644 MediaBrowser.Model/SyncPlay/SyncPlayBroadcastType.cs create mode 100644 MediaBrowser.Model/SyncPlay/SyncPlayGroupDoesNotExistUpdate.cs create mode 100644 MediaBrowser.Model/SyncPlay/SyncPlayGroupJoinedUpdate.cs create mode 100644 MediaBrowser.Model/SyncPlay/SyncPlayGroupLeftUpdate.cs create mode 100644 MediaBrowser.Model/SyncPlay/SyncPlayLibraryAccessDeniedUpdate.cs create mode 100644 MediaBrowser.Model/SyncPlay/SyncPlayNotInGroupUpdate.cs create mode 100644 MediaBrowser.Model/SyncPlay/SyncPlayPlayQueueUpdate.cs create mode 100644 MediaBrowser.Model/SyncPlay/SyncPlayQueueItem.cs create mode 100644 MediaBrowser.Model/SyncPlay/SyncPlayStateUpdate.cs create mode 100644 MediaBrowser.Model/SyncPlay/SyncPlayUserJoinedUpdate.cs create mode 100644 MediaBrowser.Model/SyncPlay/SyncPlayUserLeftUpdate.cs create mode 100644 MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs create mode 100644 MediaBrowser.Model/System/CastReceiverApplication.cs create mode 100644 MediaBrowser.Model/System/FolderStorageInfo.cs create mode 100644 MediaBrowser.Model/System/LibraryStorageInfo.cs create mode 100644 MediaBrowser.Model/System/LogFile.cs create mode 100644 MediaBrowser.Model/System/PublicSystemInfo.cs create mode 100644 MediaBrowser.Model/System/SystemInfo.cs create mode 100644 MediaBrowser.Model/System/SystemStorageInfo.cs create mode 100644 MediaBrowser.Model/Tasks/IConfigurableScheduledTask.cs create mode 100644 MediaBrowser.Model/Tasks/IScheduledTask.cs create mode 100644 MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs create mode 100644 MediaBrowser.Model/Tasks/ITaskManager.cs create mode 100644 MediaBrowser.Model/Tasks/ITaskTrigger.cs create mode 100644 MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs create mode 100644 MediaBrowser.Model/Tasks/TaskCompletionEventArgs.cs create mode 100644 MediaBrowser.Model/Tasks/TaskCompletionStatus.cs create mode 100644 MediaBrowser.Model/Tasks/TaskInfo.cs create mode 100644 MediaBrowser.Model/Tasks/TaskOptions.cs create mode 100644 MediaBrowser.Model/Tasks/TaskResult.cs create mode 100644 MediaBrowser.Model/Tasks/TaskState.cs create mode 100644 MediaBrowser.Model/Tasks/TaskTriggerInfo.cs create mode 100644 MediaBrowser.Model/Tasks/TaskTriggerInfoType.cs create mode 100644 MediaBrowser.Model/Updates/InstallationInfo.cs create mode 100644 MediaBrowser.Model/Updates/PackageInfo.cs create mode 100644 MediaBrowser.Model/Updates/RepositoryInfo.cs create mode 100644 MediaBrowser.Model/Updates/VersionInfo.cs create mode 100644 MediaBrowser.Model/Users/ForgotPasswordAction.cs create mode 100644 MediaBrowser.Model/Users/ForgotPasswordResult.cs create mode 100644 MediaBrowser.Model/Users/PinRedeemResult.cs create mode 100644 MediaBrowser.Model/Users/UserPolicy.cs create mode 100644 MediaBrowser.Model/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 MediaBrowser.Model/obj/Debug/net10.0/MediaBrowser.Model.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 MediaBrowser.Model/obj/Debug/net10.0/MediaBrowser.Model.assets.cache create mode 100644 MediaBrowser.Model/obj/Debug/net10.0/MediaBrowser.Model.csproj.AssemblyReference.cache create mode 100644 MediaBrowser.Model/obj/Debug/net10.0/NuGet/1EA50B0B60BBE767/MimeTypes/2.5.2/MimeTypes.Nullable.cs create mode 100644 MediaBrowser.Model/obj/Debug/net10.0/NuGet/2261806F2E9E3B10/MimeTypes/2.5.2/MimeTypes.Nullable.cs create mode 100644 MediaBrowser.Model/obj/MediaBrowser.Model.csproj.nuget.dgspec.json create mode 100644 MediaBrowser.Model/obj/MediaBrowser.Model.csproj.nuget.g.props create mode 100644 MediaBrowser.Model/obj/MediaBrowser.Model.csproj.nuget.g.targets create mode 100644 MediaBrowser.Model/obj/project.assets.json create mode 100644 MediaBrowser.Model/obj/project.nuget.cache create mode 100644 MediaBrowser.Model/obj/project.packagespec.json create mode 100644 MediaBrowser.Model/obj/rider.project.model.nuget.info create mode 100644 MediaBrowser.Model/obj/rider.project.restore.info create mode 100644 MediaBrowser.Providers/Books/AudioBookMetadataService.cs create mode 100644 MediaBrowser.Providers/Books/BookMetadataService.cs create mode 100644 MediaBrowser.Providers/Books/OpenPackagingFormat/EpubImageProvider.cs create mode 100644 MediaBrowser.Providers/Books/OpenPackagingFormat/EpubProvider.cs create mode 100644 MediaBrowser.Providers/Books/OpenPackagingFormat/EpubUtils.cs create mode 100644 MediaBrowser.Providers/Books/OpenPackagingFormat/OpfProvider.cs create mode 100644 MediaBrowser.Providers/Books/OpenPackagingFormat/OpfReader.cs create mode 100644 MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs create mode 100644 MediaBrowser.Providers/Channels/ChannelMetadataService.cs create mode 100644 MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs create mode 100644 MediaBrowser.Providers/Folders/FolderMetadataService.cs create mode 100644 MediaBrowser.Providers/Folders/UserViewMetadataService.cs create mode 100644 MediaBrowser.Providers/Genres/GenreMetadataService.cs create mode 100644 MediaBrowser.Providers/LiveTv/LiveTvMetadataService.cs create mode 100644 MediaBrowser.Providers/Lyric/LrcLyricParser.cs create mode 100644 MediaBrowser.Providers/Lyric/LyricManager.cs create mode 100644 MediaBrowser.Providers/Lyric/LyricScheduledTask.cs create mode 100644 MediaBrowser.Providers/Lyric/TxtLyricParser.cs create mode 100644 MediaBrowser.Providers/Manager/ImageSaver.cs create mode 100644 MediaBrowser.Providers/Manager/ItemImageProvider.cs create mode 100644 MediaBrowser.Providers/Manager/MetadataService.cs create mode 100644 MediaBrowser.Providers/Manager/ProviderManager.cs create mode 100644 MediaBrowser.Providers/Manager/RefreshResult.cs create mode 100644 MediaBrowser.Providers/MediaBrowser.Providers.csproj create mode 100644 MediaBrowser.Providers/MediaInfo/AudioFileProber.cs create mode 100644 MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs create mode 100644 MediaBrowser.Providers/MediaInfo/AudioResolver.cs create mode 100644 MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs create mode 100644 MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs create mode 100644 MediaBrowser.Providers/MediaInfo/LyricResolver.cs create mode 100644 MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs create mode 100644 MediaBrowser.Providers/MediaInfo/ProbeProvider.cs create mode 100644 MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs create mode 100644 MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs create mode 100644 MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs create mode 100644 MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs create mode 100644 MediaBrowser.Providers/Movies/ImdbExternalId.cs create mode 100644 MediaBrowser.Providers/Movies/ImdbExternalUrlProvider.cs create mode 100644 MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs create mode 100644 MediaBrowser.Providers/Movies/MovieMetadataService.cs create mode 100644 MediaBrowser.Providers/Movies/TrailerMetadataService.cs create mode 100644 MediaBrowser.Providers/Music/AlbumInfoExtensions.cs create mode 100644 MediaBrowser.Providers/Music/AlbumMetadataService.cs create mode 100644 MediaBrowser.Providers/Music/ArtistMetadataService.cs create mode 100644 MediaBrowser.Providers/Music/AudioMetadataService.cs create mode 100644 MediaBrowser.Providers/Music/ImvdbId.cs create mode 100644 MediaBrowser.Providers/Music/MusicVideoMetadataService.cs create mode 100644 MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs create mode 100644 MediaBrowser.Providers/People/PersonMetadataService.cs create mode 100644 MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs create mode 100644 MediaBrowser.Providers/Photos/PhotoMetadataService.cs create mode 100644 MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs create mode 100644 MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalUrlProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalUrlProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalUrlProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalUrlProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalUrlProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzRecordingId.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalUrlProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackExternalUrlProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs create mode 100644 MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs create mode 100644 MediaBrowser.Providers/Plugins/Omdb/Configuration/config.html create mode 100644 MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs create mode 100644 MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs create mode 100644 MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Omdb/Plugin.cs create mode 100644 MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs create mode 100644 MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html create mode 100644 MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs create mode 100644 MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TmdbExternalUrlProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs create mode 100644 MediaBrowser.Providers/Properties/AssemblyInfo.cs create mode 100644 MediaBrowser.Providers/Studios/StudioMetadataService.cs create mode 100644 MediaBrowser.Providers/Subtitles/SubtitleManager.cs create mode 100644 MediaBrowser.Providers/TV/EpisodeMetadataService.cs create mode 100644 MediaBrowser.Providers/TV/SeasonMetadataService.cs create mode 100644 MediaBrowser.Providers/TV/SeriesMetadataService.cs create mode 100644 MediaBrowser.Providers/TV/Zap2ItExternalId.cs create mode 100644 MediaBrowser.Providers/TV/Zap2ItExternalUrlProvider.cs create mode 100644 MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs create mode 100644 MediaBrowser.Providers/Trickplay/TrickplayMoveImagesTask.cs create mode 100644 MediaBrowser.Providers/Trickplay/TrickplayProvider.cs create mode 100644 MediaBrowser.Providers/Videos/VideoMetadataService.cs create mode 100644 MediaBrowser.Providers/Years/YearMetadataService.cs create mode 100644 MediaBrowser.Providers/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 MediaBrowser.Providers/obj/Debug/net10.0/MediaBrowser.Providers.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 MediaBrowser.Providers/obj/Debug/net10.0/MediaBrowser.Providers.assets.cache create mode 100644 MediaBrowser.Providers/obj/Debug/net10.0/MediaBrowser.Providers.csproj.AssemblyReference.cache create mode 100644 MediaBrowser.Providers/obj/MediaBrowser.Providers.csproj.nuget.dgspec.json create mode 100644 MediaBrowser.Providers/obj/MediaBrowser.Providers.csproj.nuget.g.props create mode 100644 MediaBrowser.Providers/obj/MediaBrowser.Providers.csproj.nuget.g.targets create mode 100644 MediaBrowser.Providers/obj/project.assets.json create mode 100644 MediaBrowser.Providers/obj/project.nuget.cache create mode 100644 MediaBrowser.Providers/obj/project.packagespec.json create mode 100644 MediaBrowser.Providers/obj/rider.project.model.nuget.info create mode 100644 MediaBrowser.Providers/obj/rider.project.restore.info create mode 100644 MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationExtension.cs create mode 100644 MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationFactory.cs create mode 100644 MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj create mode 100644 MediaBrowser.XbmcMetadata/NfoUserDataSaver.cs create mode 100644 MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs create mode 100644 MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs create mode 100644 MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs create mode 100644 MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs create mode 100644 MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs create mode 100644 MediaBrowser.XbmcMetadata/Parsers/SeriesNfoSeasonParser.cs create mode 100644 MediaBrowser.XbmcMetadata/Properties/AssemblyInfo.cs create mode 100644 MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs create mode 100644 MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs create mode 100644 MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs create mode 100644 MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs create mode 100644 MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs create mode 100644 MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs create mode 100644 MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs create mode 100644 MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs create mode 100644 MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs create mode 100644 MediaBrowser.XbmcMetadata/Providers/SeriesNfoSeasonProvider.cs create mode 100644 MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs create mode 100644 MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs create mode 100644 MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs create mode 100644 MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs create mode 100644 MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs create mode 100644 MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs create mode 100644 MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs create mode 100644 MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs create mode 100644 MediaBrowser.XbmcMetadata/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 MediaBrowser.XbmcMetadata/obj/Debug/net10.0/MediaBrowser.XbmcMetadata.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 MediaBrowser.XbmcMetadata/obj/Debug/net10.0/MediaBrowser.XbmcMetadata.assets.cache create mode 100644 MediaBrowser.XbmcMetadata/obj/Debug/net10.0/MediaBrowser.XbmcMetadata.csproj.AssemblyReference.cache create mode 100644 MediaBrowser.XbmcMetadata/obj/MediaBrowser.XbmcMetadata.csproj.nuget.dgspec.json create mode 100644 MediaBrowser.XbmcMetadata/obj/MediaBrowser.XbmcMetadata.csproj.nuget.g.props create mode 100644 MediaBrowser.XbmcMetadata/obj/MediaBrowser.XbmcMetadata.csproj.nuget.g.targets create mode 100644 MediaBrowser.XbmcMetadata/obj/project.assets.json create mode 100644 MediaBrowser.XbmcMetadata/obj/project.nuget.cache create mode 100644 MediaBrowser.XbmcMetadata/obj/project.packagespec.json create mode 100644 MediaBrowser.XbmcMetadata/obj/rider.project.model.nuget.info create mode 100644 MediaBrowser.XbmcMetadata/obj/rider.project.restore.info create mode 100644 README.md create mode 100644 SharedVersion.cs create mode 100644 bump_version create mode 100644 deployment/unraid/docker-templates/README.md create mode 100644 deployment/unraid/docker-templates/jellyfin.xml create mode 100644 fuzz/.gitignore create mode 100644 fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj create mode 100644 fuzz/Emby.Server.Implementations.Fuzz/Program.cs create mode 100644 fuzz/Emby.Server.Implementations.Fuzz/Testcases/PathExtensions.TryReplaceSubPath/test1.txt create mode 100644 fuzz/Emby.Server.Implementations.Fuzz/Testcases/SqliteItemRepository.ItemImageInfoFromValueString/test1.txt create mode 100644 fuzz/Emby.Server.Implementations.Fuzz/fuzz.sh create mode 100644 fuzz/Jellyfin.Api.Fuzz/Jellyfin.Api.Fuzz.csproj create mode 100644 fuzz/Jellyfin.Api.Fuzz/Program.cs create mode 100644 fuzz/Jellyfin.Api.Fuzz/Testcases/UrlDecodeQueryFeature/test1.txt create mode 100644 fuzz/Jellyfin.Api.Fuzz/fuzz.sh create mode 100644 fuzz/README.md create mode 100644 global.json create mode 100644 jellyfin.code-workspace create mode 100644 nuget.config create mode 100644 src/Directory.Build.props create mode 100644 src/Jellyfin.CodeAnalysis/AnalyzerReleases.Shipped.md create mode 100644 src/Jellyfin.CodeAnalysis/AsyncDisposalPatternAnalyzer.cs create mode 100644 src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj create mode 100644 src/Jellyfin.CodeAnalysis/obj/Debug/netstandard2.0/.NETStandard,Version=v2.0.AssemblyAttributes.cs create mode 100644 src/Jellyfin.CodeAnalysis/obj/Debug/netstandard2.0/Jellyfin.CodeAnalysis.AssemblyInfo.cs create mode 100644 src/Jellyfin.CodeAnalysis/obj/Debug/netstandard2.0/Jellyfin.CodeAnalysis.AssemblyInfoInputs.cache create mode 100644 src/Jellyfin.CodeAnalysis/obj/Debug/netstandard2.0/Jellyfin.CodeAnalysis.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 src/Jellyfin.CodeAnalysis/obj/Debug/netstandard2.0/Jellyfin.CodeAnalysis.assets.cache create mode 100644 src/Jellyfin.CodeAnalysis/obj/Debug/netstandard2.0/Jellyfin.CodeAnalysis.csproj.AssemblyReference.cache create mode 100644 src/Jellyfin.CodeAnalysis/obj/Jellyfin.CodeAnalysis.csproj.nuget.dgspec.json create mode 100644 src/Jellyfin.CodeAnalysis/obj/Jellyfin.CodeAnalysis.csproj.nuget.g.props create mode 100644 src/Jellyfin.CodeAnalysis/obj/Jellyfin.CodeAnalysis.csproj.nuget.g.targets create mode 100644 src/Jellyfin.CodeAnalysis/obj/project.assets.json create mode 100644 src/Jellyfin.CodeAnalysis/obj/project.nuget.cache create mode 100644 src/Jellyfin.CodeAnalysis/obj/project.packagespec.json create mode 100644 src/Jellyfin.CodeAnalysis/obj/rider.project.model.nuget.info create mode 100644 src/Jellyfin.CodeAnalysis/obj/rider.project.restore.info create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/DbConfiguration/CustomDatabaseOption.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/DbConfiguration/CustomDatabaseOptions.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/DbConfiguration/DatabaseConfigurationOptions.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/DbConfiguration/DatabaseLockingBehaviorTypes.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/AccessSchedule.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/ActivityLog.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/AncestorId.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/AttachmentStreamInfo.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/BaseItemEntity.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/BaseItemExtraType.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/BaseItemImageInfo.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/BaseItemMetadataField.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/BaseItemProvider.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/BaseItemTrailerType.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Chapter.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/CustomItemDisplayPreferences.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/DisplayPreferences.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Group.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/HomeSection.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/ImageInfo.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/ImageInfoImageType.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/ItemDisplayPreferences.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/ItemValue.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/ItemValueMap.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/ItemValueType.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/KeyframeData.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/Artwork.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/Book.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/BookMetadata.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/Chapter.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/Collection.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/CollectionItem.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/Company.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/CompanyMetadata.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/CustomItem.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/CustomItemMetadata.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/Episode.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/EpisodeMetadata.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/Genre.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/ItemMetadata.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/Library.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/LibraryItem.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/MediaFile.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/MediaFileStream.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/MetadataProvider.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/MetadataProviderId.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/Movie.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/MovieMetadata.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/MusicAlbum.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/MusicAlbumMetadata.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/Person.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/PersonRole.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/Photo.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/PhotoMetadata.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/Rating.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/RatingSource.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/Release.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/Season.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/SeasonMetadata.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/Series.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/SeriesMetadata.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/Track.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Libraries/TrackMetadata.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/MediaSegment.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/MediaStreamInfo.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/MediaStreamTypeEntity.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/People.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/PeopleBaseItemMap.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Permission.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Preference.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/ProgramAudioEntity.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Security/ApiKey.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Security/Device.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/Security/DeviceOptions.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/TrickplayInfo.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/User.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/UserData.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/ArtKind.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/ChromecastVersion.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/DynamicDayOfWeek.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/HomeSectionType.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/IndexingKind.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/MediaFileKind.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/MediaSegmentType.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/PermissionKind.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/PersonRoleType.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/PreferenceKind.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/ScrollDirection.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/SortOrder.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/SubtitlePlaybackMode.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/SyncPlayUserAccessType.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/ViewType.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/IJellyfinDatabaseProvider.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Interfaces/IHasArtwork.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Interfaces/IHasCompanies.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Interfaces/IHasConcurrencyToken.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Interfaces/IHasPermissions.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Interfaces/IHasReleases.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinDatabaseProviderKeyAttribute.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinDbContext.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinQueryHelperExtensions.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/IEntityFrameworkCoreLockingBehavior.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/NoLockBehavior.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/OptimisticLockBehavior.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/PessimisticLockBehavior.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/ActivityLogConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/AncestorIdConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/ApiKeyConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/AttachmentStreamInfoConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/BaseItemConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/BaseItemMetadataFieldConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/BaseItemProviderConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/BaseItemTrailerTypeConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/ChapterConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/CustomItemDisplayPreferencesConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/DeviceConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/DeviceOptionsConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/DisplayPreferencesConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/ItemValuesConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/ItemValuesMapConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/KeyframeDataConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/MediaStreamInfoConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/PeopleBaseItemMapConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/PeopleConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/PermissionConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/PreferenceConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/TrickplayInfoConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/UserConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/UserDataConfiguration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ProgressablePartitionReporting.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/QueryPartitionHelpers.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/Debug/net10.0/Jellyfin.Database.Implementations.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/Debug/net10.0/Jellyfin.Database.Implementations.assets.cache create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/Debug/net10.0/Jellyfin.Database.Implementations.csproj.AssemblyReference.cache create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/Jellyfin.Database.Implementations.csproj.nuget.dgspec.json create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/Jellyfin.Database.Implementations.csproj.nuget.g.props create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/Jellyfin.Database.Implementations.csproj.nuget.g.targets create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/project.assets.json create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/project.nuget.cache create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/project.packagespec.json create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/rider.project.model.nuget.info create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/rider.project.restore.info create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/DoNotUseReturningClauseConvention.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/.gitattributes create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20200514181226_AddActivityLog.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20200514181226_AddActivityLog.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20200613202153_AddUsers.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20200613202153_AddUsers.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20200728005145_AddDisplayPreferences.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20200728005145_AddDisplayPreferences.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20200905220533_FixDisplayPreferencesIndex.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20200905220533_FixDisplayPreferencesIndex.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20201004171403_AddMaxActiveSessions.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20201004171403_AddMaxActiveSessions.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20201204223655_AddCustomDisplayPreferences.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20201204223655_AddCustomDisplayPreferences.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20210320181425_AddIndexesAndCollations.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20210320181425_AddIndexesAndCollations.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20210407110544_NullableCustomPrefValue.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20210407110544_NullableCustomPrefValue.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20210814002109_AddDevices.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20210814002109_AddDevices.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20221022080052_AddIndexActivityLogsDateCreated.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20221022080052_AddIndexActivityLogsDateCreated.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20230526173516_RemoveEasyPassword.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20230526173516_RemoveEasyPassword.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20230626233818_AddTrickplayInfos.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20230626233818_AddTrickplayInfos.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20230923170422_UserCastReceiver.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20230923170422_UserCastReceiver.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20240729140605_AddMediaSegments.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20240729140605_AddMediaSegments.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20240928082930_MarkSegmentProviderIdNonNullable.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20240928082930_MarkSegmentProviderIdNonNullable.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20241020103111_LibraryDbMigration.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20241020103111_LibraryDbMigration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20241111131257_AddedCustomDataKey.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20241111131257_AddedCustomDataKey.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20241111135439_AddedCustomDataKeyKey.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20241111135439_AddedCustomDataKeyKey.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20241112152323_FixAncestorIdConfig.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20241112152323_FixAncestorIdConfig.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20241112232041_fixMediaStreams.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20241112232041_fixMediaStreams.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20241112234144_FixMediaStreams2.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20241112234144_FixMediaStreams2.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20241113133548_EnforceUniqueItemValue.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20241113133548_EnforceUniqueItemValue.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250202021306_FixedCollation.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250202021306_FixedCollation.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250204092455_MakeStartEndDateNullable.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250204092455_MakeStartEndDateNullable.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250214031148_ChannelIdGuid.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250214031148_ChannelIdGuid.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250326065026_AddInheritedParentalRatingSubValue.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250326065026_AddInheritedParentalRatingSubValue.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250327101120_AddKeyframeData.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250327101120_AddKeyframeData.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250327171413_AddHdr10PlusFlag.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250327171413_AddHdr10PlusFlag.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250331182844_FixAttachmentMigration.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250331182844_FixAttachmentMigration.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250401142247_FixAncestors.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250401142247_FixAncestors.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250405075612_FixItemValuesIndices.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250405075612_FixItemValuesIndices.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250609115616_DetachUserDataInsteadOfDelete.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250609115616_DetachUserDataInsteadOfDelete.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250622170802_BaseItemImageInfoDateModifiedNullable.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250622170802_BaseItemImageInfoDateModifiedNullable.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250714044826_ResetJournalMode.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250714044826_ResetJournalMode.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250913211637_AddProperParentChildRelationBaseItemWithCascade.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250913211637_AddProperParentChildRelationBaseItemWithCascade.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250925203415_ExtendPeopleMapKey.Designer.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/20250925203415_ExtendPeopleMapKey.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/JellyfinDbModelSnapshot.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/SqliteDesignTimeJellyfinDbFactory.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/ModelBuilderExtensions.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/PragmaConnectionInterceptor.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Properties/AssemblyInfo.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/SqliteDatabaseProvider.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/ValueConverters/DateTimeKindValueConverter.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/obj/Debug/net10.0/Jellyfin.Database.Providers.Sqlite.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/obj/Debug/net10.0/Jellyfin.Database.Providers.Sqlite.assets.cache create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/obj/Debug/net10.0/Jellyfin.Database.Providers.Sqlite.csproj.AssemblyReference.cache create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/obj/Jellyfin.Database.Providers.Sqlite.csproj.nuget.dgspec.json create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/obj/Jellyfin.Database.Providers.Sqlite.csproj.nuget.g.props create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/obj/Jellyfin.Database.Providers.Sqlite.csproj.nuget.g.targets create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/obj/project.assets.json create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/obj/project.nuget.cache create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/obj/project.packagespec.json create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/obj/rider.project.model.nuget.info create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/obj/rider.project.restore.info create mode 100644 src/Jellyfin.Database/readme.md create mode 100644 src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj create mode 100644 src/Jellyfin.Drawing.Skia/PercentPlayedDrawer.cs create mode 100644 src/Jellyfin.Drawing.Skia/Properties/AssemblyInfo.cs create mode 100644 src/Jellyfin.Drawing.Skia/SkiaEncoder.cs create mode 100644 src/Jellyfin.Drawing.Skia/SkiaExtensions.cs create mode 100644 src/Jellyfin.Drawing.Skia/SkiaHelper.cs create mode 100644 src/Jellyfin.Drawing.Skia/SplashscreenBuilder.cs create mode 100644 src/Jellyfin.Drawing.Skia/StripCollageBuilder.cs create mode 100644 src/Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs create mode 100644 src/Jellyfin.Drawing.Skia/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 src/Jellyfin.Drawing.Skia/obj/Debug/net10.0/Jellyfin.Drawing.Skia.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 src/Jellyfin.Drawing.Skia/obj/Debug/net10.0/Jellyfin.Drawing.Skia.assets.cache create mode 100644 src/Jellyfin.Drawing.Skia/obj/Debug/net10.0/Jellyfin.Drawing.Skia.csproj.AssemblyReference.cache create mode 100644 src/Jellyfin.Drawing.Skia/obj/Jellyfin.Drawing.Skia.csproj.nuget.dgspec.json create mode 100644 src/Jellyfin.Drawing.Skia/obj/Jellyfin.Drawing.Skia.csproj.nuget.g.props create mode 100644 src/Jellyfin.Drawing.Skia/obj/Jellyfin.Drawing.Skia.csproj.nuget.g.targets create mode 100644 src/Jellyfin.Drawing.Skia/obj/project.assets.json create mode 100644 src/Jellyfin.Drawing.Skia/obj/project.nuget.cache create mode 100644 src/Jellyfin.Drawing.Skia/obj/project.packagespec.json create mode 100644 src/Jellyfin.Drawing.Skia/obj/rider.project.model.nuget.info create mode 100644 src/Jellyfin.Drawing.Skia/obj/rider.project.restore.info create mode 100644 src/Jellyfin.Drawing/ImageProcessor.cs create mode 100644 src/Jellyfin.Drawing/Jellyfin.Drawing.csproj create mode 100644 src/Jellyfin.Drawing/NullImageEncoder.cs create mode 100644 src/Jellyfin.Drawing/Properties/AssemblyInfo.cs create mode 100644 src/Jellyfin.Drawing/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 src/Jellyfin.Drawing/obj/Debug/net10.0/Jellyfin.Drawing.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 src/Jellyfin.Drawing/obj/Debug/net10.0/Jellyfin.Drawing.assets.cache create mode 100644 src/Jellyfin.Drawing/obj/Debug/net10.0/Jellyfin.Drawing.csproj.AssemblyReference.cache create mode 100644 src/Jellyfin.Drawing/obj/Jellyfin.Drawing.csproj.nuget.dgspec.json create mode 100644 src/Jellyfin.Drawing/obj/Jellyfin.Drawing.csproj.nuget.g.props create mode 100644 src/Jellyfin.Drawing/obj/Jellyfin.Drawing.csproj.nuget.g.targets create mode 100644 src/Jellyfin.Drawing/obj/project.assets.json create mode 100644 src/Jellyfin.Drawing/obj/project.nuget.cache create mode 100644 src/Jellyfin.Drawing/obj/project.packagespec.json create mode 100644 src/Jellyfin.Drawing/obj/rider.project.model.nuget.info create mode 100644 src/Jellyfin.Drawing/obj/rider.project.restore.info create mode 100644 src/Jellyfin.Extensions/CopyToExtensions.cs create mode 100644 src/Jellyfin.Extensions/DictionaryExtensions.cs create mode 100644 src/Jellyfin.Extensions/EnumerableExtensions.cs create mode 100644 src/Jellyfin.Extensions/FileHelper.cs create mode 100644 src/Jellyfin.Extensions/FormattingStreamWriter.cs create mode 100644 src/Jellyfin.Extensions/GuidExtensions.cs create mode 100644 src/Jellyfin.Extensions/Jellyfin.Extensions.csproj create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonBoolNumberConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonBoolStringConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonCommaDelimitedCollectionConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonCommaDelimitedCollectionConverterFactory.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonDateTimeConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonDefaultStringEnumConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonDefaultStringEnumConverterFactory.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonDelimitedCollectionConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonFlagEnumConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonFlagEnumConverterFactory.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonGuidConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonNullableStructConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonNullableStructConverterFactory.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonPipeDelimitedCollectionConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonPipeDelimitedCollectionConverterFactory.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonStringConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonVersionConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/JsonDefaults.cs create mode 100644 src/Jellyfin.Extensions/Json/Utf8JsonExtensions.cs create mode 100644 src/Jellyfin.Extensions/ReadOnlyListExtension.cs create mode 100644 src/Jellyfin.Extensions/ShuffleExtensions.cs create mode 100644 src/Jellyfin.Extensions/SplitStringExtensions.cs create mode 100644 src/Jellyfin.Extensions/StreamExtensions.cs create mode 100644 src/Jellyfin.Extensions/StringBuilderExtensions.cs create mode 100644 src/Jellyfin.Extensions/StringExtensions.cs create mode 100644 src/Jellyfin.Extensions/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 src/Jellyfin.Extensions/obj/Debug/net10.0/Jellyfin.Extensions.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 src/Jellyfin.Extensions/obj/Debug/net10.0/Jellyfin.Extensions.assets.cache create mode 100644 src/Jellyfin.Extensions/obj/Debug/net10.0/Jellyfin.Extensions.csproj.AssemblyReference.cache create mode 100644 src/Jellyfin.Extensions/obj/Jellyfin.Extensions.csproj.nuget.dgspec.json create mode 100644 src/Jellyfin.Extensions/obj/Jellyfin.Extensions.csproj.nuget.g.props create mode 100644 src/Jellyfin.Extensions/obj/Jellyfin.Extensions.csproj.nuget.g.targets create mode 100644 src/Jellyfin.Extensions/obj/project.assets.json create mode 100644 src/Jellyfin.Extensions/obj/project.nuget.cache create mode 100644 src/Jellyfin.Extensions/obj/project.packagespec.json create mode 100644 src/Jellyfin.Extensions/obj/rider.project.model.nuget.info create mode 100644 src/Jellyfin.Extensions/obj/rider.project.restore.info create mode 100644 src/Jellyfin.LiveTv/Channels/ChannelDynamicMediaSourceProvider.cs create mode 100644 src/Jellyfin.LiveTv/Channels/ChannelImageProvider.cs create mode 100644 src/Jellyfin.LiveTv/Channels/ChannelManager.cs create mode 100644 src/Jellyfin.LiveTv/Channels/ChannelPostScanTask.cs create mode 100644 src/Jellyfin.LiveTv/Channels/RefreshChannelsScheduledTask.cs create mode 100644 src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationExtensions.cs create mode 100644 src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationFactory.cs create mode 100644 src/Jellyfin.LiveTv/DefaultLiveTvService.cs create mode 100644 src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs create mode 100644 src/Jellyfin.LiveTv/Guide/GuideManager.cs create mode 100644 src/Jellyfin.LiveTv/Guide/RefreshGuideScheduledTask.cs create mode 100644 src/Jellyfin.LiveTv/IO/DirectRecorder.cs create mode 100644 src/Jellyfin.LiveTv/IO/EncodedRecorder.cs create mode 100644 src/Jellyfin.LiveTv/IO/ExclusiveLiveStream.cs create mode 100644 src/Jellyfin.LiveTv/IO/IRecorder.cs create mode 100644 src/Jellyfin.LiveTv/IO/StreamHelper.cs create mode 100644 src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj create mode 100644 src/Jellyfin.LiveTv/Listings/EpgChannelData.cs create mode 100644 src/Jellyfin.LiveTv/Listings/ListingsManager.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/CastDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/DayDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MapDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/StationDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs create mode 100644 src/Jellyfin.LiveTv/Listings/XmlTvListingsProvider.cs create mode 100644 src/Jellyfin.LiveTv/LiveTvDtoService.cs create mode 100644 src/Jellyfin.LiveTv/LiveTvManager.cs create mode 100644 src/Jellyfin.LiveTv/LiveTvMediaSourceProvider.cs create mode 100644 src/Jellyfin.LiveTv/Recordings/RecordingHelper.cs create mode 100644 src/Jellyfin.LiveTv/Recordings/RecordingNotifier.cs create mode 100644 src/Jellyfin.LiveTv/Recordings/RecordingsHost.cs create mode 100644 src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs create mode 100644 src/Jellyfin.LiveTv/Recordings/RecordingsMetadataManager.cs create mode 100644 src/Jellyfin.LiveTv/Timers/ItemDataProvider.cs create mode 100644 src/Jellyfin.LiveTv/Timers/SeriesTimerManager.cs create mode 100644 src/Jellyfin.LiveTv/Timers/TimerManager.cs create mode 100644 src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs create mode 100644 src/Jellyfin.LiveTv/TunerHosts/HdHomerun/Channels.cs create mode 100644 src/Jellyfin.LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs create mode 100644 src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs create mode 100644 src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs create mode 100644 src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs create mode 100644 src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs create mode 100644 src/Jellyfin.LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs create mode 100644 src/Jellyfin.LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs create mode 100644 src/Jellyfin.LiveTv/TunerHosts/LiveStream.cs create mode 100644 src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs create mode 100644 src/Jellyfin.LiveTv/TunerHosts/M3uParser.cs create mode 100644 src/Jellyfin.LiveTv/TunerHosts/SharedHttpStream.cs create mode 100644 src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs create mode 100644 src/Jellyfin.LiveTv/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 src/Jellyfin.LiveTv/obj/Debug/net10.0/Jellyfin.LiveTv.AssemblyInfo.cs create mode 100644 src/Jellyfin.LiveTv/obj/Debug/net10.0/Jellyfin.LiveTv.AssemblyInfoInputs.cache create mode 100644 src/Jellyfin.LiveTv/obj/Debug/net10.0/Jellyfin.LiveTv.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 src/Jellyfin.LiveTv/obj/Debug/net10.0/Jellyfin.LiveTv.assets.cache create mode 100644 src/Jellyfin.LiveTv/obj/Debug/net10.0/Jellyfin.LiveTv.csproj.AssemblyReference.cache create mode 100644 src/Jellyfin.LiveTv/obj/Jellyfin.LiveTv.csproj.nuget.dgspec.json create mode 100644 src/Jellyfin.LiveTv/obj/Jellyfin.LiveTv.csproj.nuget.g.props create mode 100644 src/Jellyfin.LiveTv/obj/Jellyfin.LiveTv.csproj.nuget.g.targets create mode 100644 src/Jellyfin.LiveTv/obj/project.assets.json create mode 100644 src/Jellyfin.LiveTv/obj/project.nuget.cache create mode 100644 src/Jellyfin.LiveTv/obj/project.packagespec.json create mode 100644 src/Jellyfin.LiveTv/obj/rider.project.model.nuget.info create mode 100644 src/Jellyfin.LiveTv/obj/rider.project.restore.info create mode 100644 src/Jellyfin.MediaEncoding.Hls/Cache/CacheDecorator.cs create mode 100644 src/Jellyfin.MediaEncoding.Hls/Extensions/MediaEncodingHlsServiceCollectionExtensions.cs create mode 100644 src/Jellyfin.MediaEncoding.Hls/Extractors/FfProbeKeyframeExtractor.cs create mode 100644 src/Jellyfin.MediaEncoding.Hls/Extractors/IKeyframeExtractor.cs create mode 100644 src/Jellyfin.MediaEncoding.Hls/Extractors/MatroskaKeyframeExtractor.cs create mode 100644 src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj create mode 100644 src/Jellyfin.MediaEncoding.Hls/Playlist/CreateMainPlaylistRequest.cs create mode 100644 src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs create mode 100644 src/Jellyfin.MediaEncoding.Hls/Playlist/IDynamicHlsPlaylistGenerator.cs create mode 100644 src/Jellyfin.MediaEncoding.Hls/ScheduledTasks/KeyframeExtractionScheduledTask.cs create mode 100644 src/Jellyfin.MediaEncoding.Hls/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 src/Jellyfin.MediaEncoding.Hls/obj/Debug/net10.0/Jellyfin.MediaEncoding.Hls.AssemblyInfo.cs create mode 100644 src/Jellyfin.MediaEncoding.Hls/obj/Debug/net10.0/Jellyfin.MediaEncoding.Hls.AssemblyInfoInputs.cache create mode 100644 src/Jellyfin.MediaEncoding.Hls/obj/Debug/net10.0/Jellyfin.MediaEncoding.Hls.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 src/Jellyfin.MediaEncoding.Hls/obj/Debug/net10.0/Jellyfin.MediaEncoding.Hls.assets.cache create mode 100644 src/Jellyfin.MediaEncoding.Hls/obj/Debug/net10.0/Jellyfin.MediaEncoding.Hls.csproj.AssemblyReference.cache create mode 100644 src/Jellyfin.MediaEncoding.Hls/obj/Jellyfin.MediaEncoding.Hls.csproj.nuget.dgspec.json create mode 100644 src/Jellyfin.MediaEncoding.Hls/obj/Jellyfin.MediaEncoding.Hls.csproj.nuget.g.props create mode 100644 src/Jellyfin.MediaEncoding.Hls/obj/Jellyfin.MediaEncoding.Hls.csproj.nuget.g.targets create mode 100644 src/Jellyfin.MediaEncoding.Hls/obj/project.assets.json create mode 100644 src/Jellyfin.MediaEncoding.Hls/obj/project.nuget.cache create mode 100644 src/Jellyfin.MediaEncoding.Hls/obj/project.packagespec.json create mode 100644 src/Jellyfin.MediaEncoding.Hls/obj/rider.project.model.nuget.info create mode 100644 src/Jellyfin.MediaEncoding.Hls/obj/rider.project.restore.info create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/FfProbe/FfProbeKeyframeExtractor.cs create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/FfTool/FfToolKeyframeExtractor.cs create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/KeyframeData.cs create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/Matroska/Extensions/EbmlReaderExtensions.cs create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaConstants.cs create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaKeyframeExtractor.cs create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/Matroska/Models/Info.cs create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/Matroska/Models/SeekHead.cs create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/obj/Debug/net10.0/Jellyfin.MediaEncoding.Keyframes.AssemblyInfo.cs create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/obj/Debug/net10.0/Jellyfin.MediaEncoding.Keyframes.AssemblyInfoInputs.cache create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/obj/Debug/net10.0/Jellyfin.MediaEncoding.Keyframes.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/obj/Debug/net10.0/Jellyfin.MediaEncoding.Keyframes.assets.cache create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/obj/Debug/net10.0/Jellyfin.MediaEncoding.Keyframes.csproj.AssemblyReference.cache create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/obj/Jellyfin.MediaEncoding.Keyframes.csproj.nuget.dgspec.json create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/obj/Jellyfin.MediaEncoding.Keyframes.csproj.nuget.g.props create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/obj/Jellyfin.MediaEncoding.Keyframes.csproj.nuget.g.targets create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/obj/project.assets.json create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/obj/project.nuget.cache create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/obj/project.packagespec.json create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/obj/rider.project.model.nuget.info create mode 100644 src/Jellyfin.MediaEncoding.Keyframes/obj/rider.project.restore.info create mode 100644 src/Jellyfin.Networking/AutoDiscoveryHost.cs create mode 100644 src/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs create mode 100644 src/Jellyfin.Networking/Jellyfin.Networking.csproj create mode 100644 src/Jellyfin.Networking/Manager/NetworkManager.cs create mode 100644 src/Jellyfin.Networking/Udp/SocketFactory.cs create mode 100644 src/Jellyfin.Networking/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 src/Jellyfin.Networking/obj/Debug/net10.0/Jellyfin.Networking.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 src/Jellyfin.Networking/obj/Debug/net10.0/Jellyfin.Networking.assets.cache create mode 100644 src/Jellyfin.Networking/obj/Debug/net10.0/Jellyfin.Networking.csproj.AssemblyReference.cache create mode 100644 src/Jellyfin.Networking/obj/Jellyfin.Networking.csproj.nuget.dgspec.json create mode 100644 src/Jellyfin.Networking/obj/Jellyfin.Networking.csproj.nuget.g.props create mode 100644 src/Jellyfin.Networking/obj/Jellyfin.Networking.csproj.nuget.g.targets create mode 100644 src/Jellyfin.Networking/obj/project.assets.json create mode 100644 src/Jellyfin.Networking/obj/project.nuget.cache create mode 100644 src/Jellyfin.Networking/obj/project.packagespec.json create mode 100644 src/Jellyfin.Networking/obj/rider.project.model.nuget.info create mode 100644 src/Jellyfin.Networking/obj/rider.project.restore.info create mode 100644 stylecop.json create mode 100644 tests/Directory.Build.props create mode 100644 tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs create mode 100644 tests/Jellyfin.Api.Tests/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandlerTests.cs create mode 100644 tests/Jellyfin.Api.Tests/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandlerTests.cs create mode 100644 tests/Jellyfin.Api.Tests/Auth/IgnoreSchedulePolicy/IgnoreScheduleHandlerTests.cs create mode 100644 tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs create mode 100644 tests/Jellyfin.Api.Tests/Controllers/ImageControllerTests.cs create mode 100644 tests/Jellyfin.Api.Tests/Controllers/SystemControllerTests.cs create mode 100644 tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs create mode 100644 tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs create mode 100644 tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj create mode 100644 tests/Jellyfin.Api.Tests/Middleware/UrlDecodeQueryFeatureTests.cs create mode 100644 tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedCollectionModelBinderTests.cs create mode 100644 tests/Jellyfin.Api.Tests/ModelBinders/PipeDelimitedCollectionModelBinderTests.cs create mode 100644 tests/Jellyfin.Api.Tests/ModelBinders/TestType.cs create mode 100644 tests/Jellyfin.Api.Tests/TestHelpers.cs create mode 100644 tests/Jellyfin.Api.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping_Jellyfin.Api.Tests create mode 100644 tests/Jellyfin.Api.Tests/bin/Debug/net10.0/CoverletSourceRootsMapping_Jellyfin.Api.Tests create mode 100644 tests/Jellyfin.Api.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 tests/Jellyfin.Api.Tests/obj/Debug/net10.0/Jellyfin.Api.Tests.AssemblyInfo.cs create mode 100644 tests/Jellyfin.Api.Tests/obj/Debug/net10.0/Jellyfin.Api.Tests.AssemblyInfoInputs.cache create mode 100644 tests/Jellyfin.Api.Tests/obj/Debug/net10.0/Jellyfin.Api.Tests.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 tests/Jellyfin.Api.Tests/obj/Debug/net10.0/Jellyfin.Api.Tests.assets.cache create mode 100644 tests/Jellyfin.Api.Tests/obj/Debug/net10.0/Jellyfin.Api.Tests.csproj.AssemblyReference.cache create mode 100644 tests/Jellyfin.Api.Tests/obj/Jellyfin.Api.Tests.csproj.nuget.dgspec.json create mode 100644 tests/Jellyfin.Api.Tests/obj/Jellyfin.Api.Tests.csproj.nuget.g.props create mode 100644 tests/Jellyfin.Api.Tests/obj/Jellyfin.Api.Tests.csproj.nuget.g.targets create mode 100644 tests/Jellyfin.Api.Tests/obj/project.assets.json create mode 100644 tests/Jellyfin.Api.Tests/obj/project.nuget.cache create mode 100644 tests/Jellyfin.Api.Tests/obj/project.packagespec.json create mode 100644 tests/Jellyfin.Api.Tests/obj/rider.project.model.nuget.info create mode 100644 tests/Jellyfin.Api.Tests/obj/rider.project.restore.info create mode 100644 tests/Jellyfin.Common.Tests/Crc32Tests.cs create mode 100644 tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj create mode 100644 tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs create mode 100644 tests/Jellyfin.Common.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping_Jellyfin.Common.Tests create mode 100644 tests/Jellyfin.Common.Tests/bin/Debug/net10.0/CoverletSourceRootsMapping_Jellyfin.Common.Tests create mode 100644 tests/Jellyfin.Common.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 tests/Jellyfin.Common.Tests/obj/Debug/net10.0/Jellyfin.Common.Tests.AssemblyInfo.cs create mode 100644 tests/Jellyfin.Common.Tests/obj/Debug/net10.0/Jellyfin.Common.Tests.AssemblyInfoInputs.cache create mode 100644 tests/Jellyfin.Common.Tests/obj/Debug/net10.0/Jellyfin.Common.Tests.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 tests/Jellyfin.Common.Tests/obj/Debug/net10.0/Jellyfin.Common.Tests.assets.cache create mode 100644 tests/Jellyfin.Common.Tests/obj/Debug/net10.0/Jellyfin.Common.Tests.csproj.AssemblyReference.cache create mode 100644 tests/Jellyfin.Common.Tests/obj/Jellyfin.Common.Tests.csproj.nuget.dgspec.json create mode 100644 tests/Jellyfin.Common.Tests/obj/Jellyfin.Common.Tests.csproj.nuget.g.props create mode 100644 tests/Jellyfin.Common.Tests/obj/Jellyfin.Common.Tests.csproj.nuget.g.targets create mode 100644 tests/Jellyfin.Common.Tests/obj/project.assets.json create mode 100644 tests/Jellyfin.Common.Tests/obj/project.nuget.cache create mode 100644 tests/Jellyfin.Common.Tests/obj/project.packagespec.json create mode 100644 tests/Jellyfin.Common.Tests/obj/rider.project.model.nuget.info create mode 100644 tests/Jellyfin.Common.Tests/obj/rider.project.restore.info create mode 100644 tests/Jellyfin.Controller.Tests/BaseItemManagerTests.cs create mode 100644 tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs create mode 100644 tests/Jellyfin.Controller.Tests/Entities/BaseItemTests.cs create mode 100644 tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj create mode 100644 tests/Jellyfin.Controller.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping_Jellyfin.Controller.Tests create mode 100644 tests/Jellyfin.Controller.Tests/bin/Debug/net10.0/CoverletSourceRootsMapping_Jellyfin.Controller.Tests create mode 100644 tests/Jellyfin.Controller.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 tests/Jellyfin.Controller.Tests/obj/Debug/net10.0/Jellyfin.Controller.Tests.AssemblyInfo.cs create mode 100644 tests/Jellyfin.Controller.Tests/obj/Debug/net10.0/Jellyfin.Controller.Tests.AssemblyInfoInputs.cache create mode 100644 tests/Jellyfin.Controller.Tests/obj/Debug/net10.0/Jellyfin.Controller.Tests.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 tests/Jellyfin.Controller.Tests/obj/Debug/net10.0/Jellyfin.Controller.Tests.assets.cache create mode 100644 tests/Jellyfin.Controller.Tests/obj/Debug/net10.0/Jellyfin.Controller.Tests.csproj.AssemblyReference.cache create mode 100644 tests/Jellyfin.Controller.Tests/obj/Jellyfin.Controller.Tests.csproj.nuget.dgspec.json create mode 100644 tests/Jellyfin.Controller.Tests/obj/Jellyfin.Controller.Tests.csproj.nuget.g.props create mode 100644 tests/Jellyfin.Controller.Tests/obj/Jellyfin.Controller.Tests.csproj.nuget.g.targets create mode 100644 tests/Jellyfin.Controller.Tests/obj/project.assets.json create mode 100644 tests/Jellyfin.Controller.Tests/obj/project.nuget.cache create mode 100644 tests/Jellyfin.Controller.Tests/obj/project.packagespec.json create mode 100644 tests/Jellyfin.Controller.Tests/obj/rider.project.model.nuget.info create mode 100644 tests/Jellyfin.Controller.Tests/obj/rider.project.restore.info create mode 100644 tests/Jellyfin.Extensions.Tests/CopyToExtensionsTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/FileHelperTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/FormattingStreamWriterTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonBoolNumberTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonBoolStringTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedCollectionTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedIReadOnlyListTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonDefaultStringEnumConverterTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonFlagEnumTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonGuidConverterTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonNullableGuidConverterTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonStringConverterTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonVersionConverterTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyArrayModel.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyCollectionModel.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyListModel.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyListModel.cs create mode 100644 tests/Jellyfin.Extensions.Tests/ShuffleExtensionsTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping_Jellyfin.Extensions.Tests create mode 100644 tests/Jellyfin.Extensions.Tests/bin/Debug/net10.0/CoverletSourceRootsMapping_Jellyfin.Extensions.Tests create mode 100644 tests/Jellyfin.Extensions.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 tests/Jellyfin.Extensions.Tests/obj/Debug/net10.0/Jellyfin.Extensions.Tests.AssemblyInfo.cs create mode 100644 tests/Jellyfin.Extensions.Tests/obj/Debug/net10.0/Jellyfin.Extensions.Tests.AssemblyInfoInputs.cache create mode 100644 tests/Jellyfin.Extensions.Tests/obj/Debug/net10.0/Jellyfin.Extensions.Tests.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 tests/Jellyfin.Extensions.Tests/obj/Debug/net10.0/Jellyfin.Extensions.Tests.assets.cache create mode 100644 tests/Jellyfin.Extensions.Tests/obj/Debug/net10.0/Jellyfin.Extensions.Tests.csproj.AssemblyReference.cache create mode 100644 tests/Jellyfin.Extensions.Tests/obj/Jellyfin.Extensions.Tests.csproj.nuget.dgspec.json create mode 100644 tests/Jellyfin.Extensions.Tests/obj/Jellyfin.Extensions.Tests.csproj.nuget.g.props create mode 100644 tests/Jellyfin.Extensions.Tests/obj/Jellyfin.Extensions.Tests.csproj.nuget.g.targets create mode 100644 tests/Jellyfin.Extensions.Tests/obj/project.assets.json create mode 100644 tests/Jellyfin.Extensions.Tests/obj/project.nuget.cache create mode 100644 tests/Jellyfin.Extensions.Tests/obj/project.packagespec.json create mode 100644 tests/Jellyfin.Extensions.Tests/obj/rider.project.model.nuget.info create mode 100644 tests/Jellyfin.Extensions.Tests/obj/rider.project.restore.info create mode 100644 tests/Jellyfin.LiveTv.Tests/HdHomerunHostTests.cs create mode 100644 tests/Jellyfin.LiveTv.Tests/HdHomerunManagerTests.cs create mode 100644 tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj create mode 100644 tests/Jellyfin.LiveTv.Tests/Listings/ListingsManagerTests.cs create mode 100644 tests/Jellyfin.LiveTv.Tests/Listings/XmlTvListingsProviderTests.cs create mode 100644 tests/Jellyfin.LiveTv.Tests/RecordingHelperTests.cs create mode 100644 tests/Jellyfin.LiveTv.Tests/SchedulesDirect/SchedulesDirectDeserializeTests.cs create mode 100644 tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/10.10.10.100/discover.json create mode 100644 tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/10.10.10.100/lineup.json create mode 100644 tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/192.168.1.182/discover.json create mode 100644 tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/192.168.1.182/lineup.json create mode 100644 tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/emptycategory.xml create mode 100644 tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/notitle.xml create mode 100644 tests/Jellyfin.LiveTv.Tests/Test Data/SchedulesDirect/headends_response.json create mode 100644 tests/Jellyfin.LiveTv.Tests/Test Data/SchedulesDirect/lineup_response.json create mode 100644 tests/Jellyfin.LiveTv.Tests/Test Data/SchedulesDirect/lineups_response.json create mode 100644 tests/Jellyfin.LiveTv.Tests/Test Data/SchedulesDirect/metadata_programs_response.json create mode 100644 tests/Jellyfin.LiveTv.Tests/Test Data/SchedulesDirect/programs_response.json create mode 100644 tests/Jellyfin.LiveTv.Tests/Test Data/SchedulesDirect/schedules_request.json create mode 100644 tests/Jellyfin.LiveTv.Tests/Test Data/SchedulesDirect/schedules_response.json create mode 100644 tests/Jellyfin.LiveTv.Tests/Test Data/SchedulesDirect/token_live_response.json create mode 100644 tests/Jellyfin.LiveTv.Tests/Test Data/SchedulesDirect/token_offline_response.json create mode 100644 tests/Jellyfin.LiveTv.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping_Jellyfin.LiveTv.Tests create mode 100644 tests/Jellyfin.LiveTv.Tests/bin/Debug/net10.0/CoverletSourceRootsMapping_Jellyfin.LiveTv.Tests create mode 100644 tests/Jellyfin.LiveTv.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 tests/Jellyfin.LiveTv.Tests/obj/Debug/net10.0/Jellyfin.LiveTv.Tests.AssemblyInfo.cs create mode 100644 tests/Jellyfin.LiveTv.Tests/obj/Debug/net10.0/Jellyfin.LiveTv.Tests.AssemblyInfoInputs.cache create mode 100644 tests/Jellyfin.LiveTv.Tests/obj/Debug/net10.0/Jellyfin.LiveTv.Tests.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 tests/Jellyfin.LiveTv.Tests/obj/Debug/net10.0/Jellyfin.LiveTv.Tests.assets.cache create mode 100644 tests/Jellyfin.LiveTv.Tests/obj/Debug/net10.0/Jellyfin.LiveTv.Tests.csproj.AssemblyReference.cache create mode 100644 tests/Jellyfin.LiveTv.Tests/obj/Jellyfin.LiveTv.Tests.csproj.nuget.dgspec.json create mode 100644 tests/Jellyfin.LiveTv.Tests/obj/Jellyfin.LiveTv.Tests.csproj.nuget.g.props create mode 100644 tests/Jellyfin.LiveTv.Tests/obj/Jellyfin.LiveTv.Tests.csproj.nuget.g.targets create mode 100644 tests/Jellyfin.LiveTv.Tests/obj/project.assets.json create mode 100644 tests/Jellyfin.LiveTv.Tests/obj/project.nuget.cache create mode 100644 tests/Jellyfin.LiveTv.Tests/obj/project.packagespec.json create mode 100644 tests/Jellyfin.LiveTv.Tests/obj/rider.project.model.nuget.info create mode 100644 tests/Jellyfin.LiveTv.Tests/obj/rider.project.restore.info create mode 100644 tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj create mode 100644 tests/Jellyfin.MediaEncoding.Hls.Tests/Playlist/DynamicHlsPlaylistGeneratorTests.cs create mode 100644 tests/Jellyfin.MediaEncoding.Hls.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping_Jellyfin.MediaEncoding.Hls.Tests create mode 100644 tests/Jellyfin.MediaEncoding.Hls.Tests/bin/Debug/net10.0/CoverletSourceRootsMapping_Jellyfin.MediaEncoding.Hls.Tests create mode 100644 tests/Jellyfin.MediaEncoding.Hls.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 tests/Jellyfin.MediaEncoding.Hls.Tests/obj/Debug/net10.0/Jellyfin.MediaEncoding.Hls.Tests.AssemblyInfo.cs create mode 100644 tests/Jellyfin.MediaEncoding.Hls.Tests/obj/Debug/net10.0/Jellyfin.MediaEncoding.Hls.Tests.AssemblyInfoInputs.cache create mode 100644 tests/Jellyfin.MediaEncoding.Hls.Tests/obj/Debug/net10.0/Jellyfin.MediaEncoding.Hls.Tests.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 tests/Jellyfin.MediaEncoding.Hls.Tests/obj/Debug/net10.0/Jellyfin.MediaEncoding.Hls.Tests.assets.cache create mode 100644 tests/Jellyfin.MediaEncoding.Hls.Tests/obj/Debug/net10.0/Jellyfin.MediaEncoding.Hls.Tests.csproj.AssemblyReference.cache create mode 100644 tests/Jellyfin.MediaEncoding.Hls.Tests/obj/Jellyfin.MediaEncoding.Hls.Tests.csproj.nuget.dgspec.json create mode 100644 tests/Jellyfin.MediaEncoding.Hls.Tests/obj/Jellyfin.MediaEncoding.Hls.Tests.csproj.nuget.g.props create mode 100644 tests/Jellyfin.MediaEncoding.Hls.Tests/obj/Jellyfin.MediaEncoding.Hls.Tests.csproj.nuget.g.targets create mode 100644 tests/Jellyfin.MediaEncoding.Hls.Tests/obj/project.assets.json create mode 100644 tests/Jellyfin.MediaEncoding.Hls.Tests/obj/project.nuget.cache create mode 100644 tests/Jellyfin.MediaEncoding.Hls.Tests/obj/project.packagespec.json create mode 100644 tests/Jellyfin.MediaEncoding.Hls.Tests/obj/rider.project.model.nuget.info create mode 100644 tests/Jellyfin.MediaEncoding.Hls.Tests/obj/rider.project.restore.info create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/FfProbeKeyframeExtractorTests.cs create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/Test Data/keyframes.txt create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/Test Data/keyframes_result.json create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/Test Data/keyframes_streamduration.txt create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/Test Data/keyframes_streamduration_result.json create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping_Jellyfin.MediaEncoding.Keyframes.Tests create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/bin/Debug/net10.0/CoverletSourceRootsMapping_Jellyfin.MediaEncoding.Keyframes.Tests create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/obj/Debug/net10.0/Jellyfin.MediaEncoding.Keyframes.Tests.AssemblyInfo.cs create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/obj/Debug/net10.0/Jellyfin.MediaEncoding.Keyframes.Tests.AssemblyInfoInputs.cache create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/obj/Debug/net10.0/Jellyfin.MediaEncoding.Keyframes.Tests.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/obj/Debug/net10.0/Jellyfin.MediaEncoding.Keyframes.Tests.assets.cache create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/obj/Debug/net10.0/Jellyfin.MediaEncoding.Keyframes.Tests.csproj.AssemblyReference.cache create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/obj/Jellyfin.MediaEncoding.Keyframes.Tests.csproj.nuget.dgspec.json create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/obj/Jellyfin.MediaEncoding.Keyframes.Tests.csproj.nuget.g.props create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/obj/Jellyfin.MediaEncoding.Keyframes.Tests.csproj.nuget.g.targets create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/obj/project.assets.json create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/obj/project.nuget.cache create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/obj/project.packagespec.json create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/obj/rider.project.model.nuget.info create mode 100644 tests/Jellyfin.MediaEncoding.Keyframes.Tests/obj/rider.project.restore.info create mode 100644 tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs create mode 100644 tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeExternalSourcesTests.cs create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_metadata.json create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_video_metadata.json create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_year_only_metadata.json create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_interlaced.json create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_metadata.json create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_mp4_metadata.json create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_progressive_no_field_order.json create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_progressive_no_field_order2.json create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_single_frame_mjpeg.json create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_ts.json create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_web_like_mkv_with_subtitle.json create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_webm.json create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/example.ass create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/example.srt create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/example.ssa create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/example2.srt create mode 100644 tests/Jellyfin.MediaEncoding.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping_Jellyfin.MediaEncoding.Tests create mode 100644 tests/Jellyfin.MediaEncoding.Tests/bin/Debug/net10.0/CoverletSourceRootsMapping_Jellyfin.MediaEncoding.Tests create mode 100644 tests/Jellyfin.MediaEncoding.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 tests/Jellyfin.MediaEncoding.Tests/obj/Debug/net10.0/Jellyfin.MediaEncoding.Tests.AssemblyInfo.cs create mode 100644 tests/Jellyfin.MediaEncoding.Tests/obj/Debug/net10.0/Jellyfin.MediaEncoding.Tests.AssemblyInfoInputs.cache create mode 100644 tests/Jellyfin.MediaEncoding.Tests/obj/Debug/net10.0/Jellyfin.MediaEncoding.Tests.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 tests/Jellyfin.MediaEncoding.Tests/obj/Debug/net10.0/Jellyfin.MediaEncoding.Tests.assets.cache create mode 100644 tests/Jellyfin.MediaEncoding.Tests/obj/Debug/net10.0/Jellyfin.MediaEncoding.Tests.csproj.AssemblyReference.cache create mode 100644 tests/Jellyfin.MediaEncoding.Tests/obj/Jellyfin.MediaEncoding.Tests.csproj.nuget.dgspec.json create mode 100644 tests/Jellyfin.MediaEncoding.Tests/obj/Jellyfin.MediaEncoding.Tests.csproj.nuget.g.props create mode 100644 tests/Jellyfin.MediaEncoding.Tests/obj/Jellyfin.MediaEncoding.Tests.csproj.nuget.g.targets create mode 100644 tests/Jellyfin.MediaEncoding.Tests/obj/project.assets.json create mode 100644 tests/Jellyfin.MediaEncoding.Tests/obj/project.nuget.cache create mode 100644 tests/Jellyfin.MediaEncoding.Tests/obj/project.packagespec.json create mode 100644 tests/Jellyfin.MediaEncoding.Tests/obj/rider.project.model.nuget.info create mode 100644 tests/Jellyfin.MediaEncoding.Tests/obj/rider.project.restore.info create mode 100644 tests/Jellyfin.Model.Tests/Cryptography/PasswordHashTests.cs create mode 100644 tests/Jellyfin.Model.Tests/Dlna/ContainerHelperTests.cs create mode 100644 tests/Jellyfin.Model.Tests/Dlna/LegacyStreamInfo.cs create mode 100644 tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs create mode 100644 tests/Jellyfin.Model.Tests/Dlna/StreamInfoTests.cs create mode 100644 tests/Jellyfin.Model.Tests/Drawing/ImageFormatExtensionsTests.cs create mode 100644 tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs create mode 100644 tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs create mode 100644 tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs create mode 100644 tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj create mode 100644 tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs create mode 100644 tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidPixel.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidTVExoPlayer.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-DirectMedia.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Firefox.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-LowBandwidth.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Null.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlus.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-SafariNext.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-TranscodeMedia.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-WebOS-23.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse2.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-av1-aac-srt-2600k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-av1-vorbis-srt-2600k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-dvhe.05-eac3-28000k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-dvhe.08-eac3-15200k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-h264-ac3-srt-2600k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-h264-hi10p-aac-5000k-brokenfps.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-aac-srt-2600k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-ac3-srt-2600k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-srt-2600k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-vtt-2600k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-dvh1.05-eac3-15200k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-dvhe.08-eac3-15200k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-srt-2600k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-vtt-2600k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aac-aac-srt-2600k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aac-mp3-srt-2600k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aac-srt-2600k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacDef-srt-2600k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacExt-srt-2600k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-srt-2600k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-dts-srt-2600k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-hi10p-aac-5000k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-aac-srt-15200k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aac-srt-15200k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aacDef-srt-15200k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aacExt-srt-15200k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-srt-15200k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-truehd-srt-15200k.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-no-streams.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-numstreams-32.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-numstreams-33.json create mode 100644 tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-raw.json create mode 100644 tests/Jellyfin.Model.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping_Jellyfin.Model.Tests create mode 100644 tests/Jellyfin.Model.Tests/bin/Debug/net10.0/CoverletSourceRootsMapping_Jellyfin.Model.Tests create mode 100644 tests/Jellyfin.Model.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 tests/Jellyfin.Model.Tests/obj/Debug/net10.0/Jellyfin.Model.Tests.AssemblyInfo.cs create mode 100644 tests/Jellyfin.Model.Tests/obj/Debug/net10.0/Jellyfin.Model.Tests.AssemblyInfoInputs.cache create mode 100644 tests/Jellyfin.Model.Tests/obj/Debug/net10.0/Jellyfin.Model.Tests.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 tests/Jellyfin.Model.Tests/obj/Debug/net10.0/Jellyfin.Model.Tests.assets.cache create mode 100644 tests/Jellyfin.Model.Tests/obj/Debug/net10.0/Jellyfin.Model.Tests.csproj.AssemblyReference.cache create mode 100644 tests/Jellyfin.Model.Tests/obj/Jellyfin.Model.Tests.csproj.nuget.dgspec.json create mode 100644 tests/Jellyfin.Model.Tests/obj/Jellyfin.Model.Tests.csproj.nuget.g.props create mode 100644 tests/Jellyfin.Model.Tests/obj/Jellyfin.Model.Tests.csproj.nuget.g.targets create mode 100644 tests/Jellyfin.Model.Tests/obj/project.assets.json create mode 100644 tests/Jellyfin.Model.Tests/obj/project.nuget.cache create mode 100644 tests/Jellyfin.Model.Tests/obj/project.packagespec.json create mode 100644 tests/Jellyfin.Model.Tests/obj/rider.project.model.nuget.info create mode 100644 tests/Jellyfin.Model.Tests/obj/rider.project.restore.info create mode 100644 tests/Jellyfin.Naming.Tests/AudioBook/AudioBookFileInfoTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs create mode 100644 tests/Jellyfin.Naming.Tests/ExternalFiles/ExternalPathParserTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj create mode 100644 tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs create mode 100644 tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/TV/SeasonPathParserTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/TV/SeriesPathParserTest.cs create mode 100644 tests/Jellyfin.Naming.Tests/TV/SeriesResolverTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/TV/TvParserHelpersTest.cs create mode 100644 tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/Video/StackTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/Video/StubTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs create mode 100644 tests/Jellyfin.Naming.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping_Jellyfin.Naming.Tests create mode 100644 tests/Jellyfin.Naming.Tests/bin/Debug/net10.0/CoverletSourceRootsMapping_Jellyfin.Naming.Tests create mode 100644 tests/Jellyfin.Naming.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 tests/Jellyfin.Naming.Tests/obj/Debug/net10.0/Jellyfin.Naming.Tests.AssemblyInfo.cs create mode 100644 tests/Jellyfin.Naming.Tests/obj/Debug/net10.0/Jellyfin.Naming.Tests.AssemblyInfoInputs.cache create mode 100644 tests/Jellyfin.Naming.Tests/obj/Debug/net10.0/Jellyfin.Naming.Tests.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 tests/Jellyfin.Naming.Tests/obj/Debug/net10.0/Jellyfin.Naming.Tests.assets.cache create mode 100644 tests/Jellyfin.Naming.Tests/obj/Debug/net10.0/Jellyfin.Naming.Tests.csproj.AssemblyReference.cache create mode 100644 tests/Jellyfin.Naming.Tests/obj/Jellyfin.Naming.Tests.csproj.nuget.dgspec.json create mode 100644 tests/Jellyfin.Naming.Tests/obj/Jellyfin.Naming.Tests.csproj.nuget.g.props create mode 100644 tests/Jellyfin.Naming.Tests/obj/Jellyfin.Naming.Tests.csproj.nuget.g.targets create mode 100644 tests/Jellyfin.Naming.Tests/obj/project.assets.json create mode 100644 tests/Jellyfin.Naming.Tests/obj/project.nuget.cache create mode 100644 tests/Jellyfin.Naming.Tests/obj/project.packagespec.json create mode 100644 tests/Jellyfin.Naming.Tests/obj/rider.project.model.nuget.info create mode 100644 tests/Jellyfin.Naming.Tests/obj/rider.project.restore.info create mode 100644 tests/Jellyfin.Networking.Tests/Configuration/NetworkConfigurationTests.cs create mode 100644 tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj create mode 100644 tests/Jellyfin.Networking.Tests/NetworkExtensionsTests.cs create mode 100644 tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs create mode 100644 tests/Jellyfin.Networking.Tests/NetworkParseTests.cs create mode 100644 tests/Jellyfin.Networking.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping_Jellyfin.Networking.Tests create mode 100644 tests/Jellyfin.Networking.Tests/bin/Debug/net10.0/CoverletSourceRootsMapping_Jellyfin.Networking.Tests create mode 100644 tests/Jellyfin.Networking.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 tests/Jellyfin.Networking.Tests/obj/Debug/net10.0/Jellyfin.Networking.Tests.AssemblyInfo.cs create mode 100644 tests/Jellyfin.Networking.Tests/obj/Debug/net10.0/Jellyfin.Networking.Tests.AssemblyInfoInputs.cache create mode 100644 tests/Jellyfin.Networking.Tests/obj/Debug/net10.0/Jellyfin.Networking.Tests.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 tests/Jellyfin.Networking.Tests/obj/Debug/net10.0/Jellyfin.Networking.Tests.assets.cache create mode 100644 tests/Jellyfin.Networking.Tests/obj/Debug/net10.0/Jellyfin.Networking.Tests.csproj.AssemblyReference.cache create mode 100644 tests/Jellyfin.Networking.Tests/obj/Jellyfin.Networking.Tests.csproj.nuget.dgspec.json create mode 100644 tests/Jellyfin.Networking.Tests/obj/Jellyfin.Networking.Tests.csproj.nuget.g.props create mode 100644 tests/Jellyfin.Networking.Tests/obj/Jellyfin.Networking.Tests.csproj.nuget.g.targets create mode 100644 tests/Jellyfin.Networking.Tests/obj/project.assets.json create mode 100644 tests/Jellyfin.Networking.Tests/obj/project.nuget.cache create mode 100644 tests/Jellyfin.Networking.Tests/obj/project.packagespec.json create mode 100644 tests/Jellyfin.Networking.Tests/obj/rider.project.model.nuget.info create mode 100644 tests/Jellyfin.Networking.Tests/obj/rider.project.restore.info create mode 100644 tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj create mode 100644 tests/Jellyfin.Providers.Tests/Lyrics/LrcLyricParserTests.cs create mode 100644 tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs create mode 100644 tests/Jellyfin.Providers.Tests/Manager/MetadataServiceTests.cs create mode 100644 tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs create mode 100644 tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs create mode 100644 tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs create mode 100644 tests/Jellyfin.Providers.Tests/MediaInfo/FFProbeVideoInfoTests.cs create mode 100644 tests/Jellyfin.Providers.Tests/MediaInfo/MediaInfoResolverTests.cs create mode 100644 tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs create mode 100644 tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs create mode 100644 tests/Jellyfin.Providers.Tests/Omdb/JsonOmdbConverterTests.cs create mode 100644 tests/Jellyfin.Providers.Tests/Test Data/Images/blank0.jpg create mode 100644 tests/Jellyfin.Providers.Tests/Test Data/Images/blank1.jpg create mode 100644 tests/Jellyfin.Providers.Tests/Test Data/Lyrics/Fleetwood Mac - Rumors.elrc create mode 100644 tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs create mode 100644 tests/Jellyfin.Providers.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping_Jellyfin.Providers.Tests create mode 100644 tests/Jellyfin.Providers.Tests/bin/Debug/net10.0/CoverletSourceRootsMapping_Jellyfin.Providers.Tests create mode 100644 tests/Jellyfin.Providers.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 tests/Jellyfin.Providers.Tests/obj/Debug/net10.0/Jellyfin.Providers.Tests.AssemblyInfo.cs create mode 100644 tests/Jellyfin.Providers.Tests/obj/Debug/net10.0/Jellyfin.Providers.Tests.AssemblyInfoInputs.cache create mode 100644 tests/Jellyfin.Providers.Tests/obj/Debug/net10.0/Jellyfin.Providers.Tests.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 tests/Jellyfin.Providers.Tests/obj/Debug/net10.0/Jellyfin.Providers.Tests.assets.cache create mode 100644 tests/Jellyfin.Providers.Tests/obj/Debug/net10.0/Jellyfin.Providers.Tests.csproj.AssemblyReference.cache create mode 100644 tests/Jellyfin.Providers.Tests/obj/Jellyfin.Providers.Tests.csproj.nuget.dgspec.json create mode 100644 tests/Jellyfin.Providers.Tests/obj/Jellyfin.Providers.Tests.csproj.nuget.g.props create mode 100644 tests/Jellyfin.Providers.Tests/obj/Jellyfin.Providers.Tests.csproj.nuget.g.targets create mode 100644 tests/Jellyfin.Providers.Tests/obj/project.assets.json create mode 100644 tests/Jellyfin.Providers.Tests/obj/project.nuget.cache create mode 100644 tests/Jellyfin.Providers.Tests/obj/project.packagespec.json create mode 100644 tests/Jellyfin.Providers.Tests/obj/rider.project.model.nuget.info create mode 100644 tests/Jellyfin.Providers.Tests/obj/rider.project.restore.info create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Cryptography/CryptographyProviderTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Data/SearchPunctuationTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/EfMigrations/EfMigrationTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Item/BaseItemRepositoryTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Item/OrderMapperTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Library/AudioResolverTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Library/CoreResolutionIgnoreRuleTest.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Library/DotIgnoreIgnoreRuleTest.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Library/MediaSourceManagerTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Library/MovieResolverTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Playlists/PlaylistManagerTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/QuickConnect/QuickConnectManagerTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/SessionManager/SessionManagerTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Sorting/IndexNumberComparerTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Sorting/ParentIndexNumberComparerTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Sorting/PremiereDateComparerTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ForceKeepAlive.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/Partial.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ValidPartial.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/empty.zip create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/manifest.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping_Jellyfin.Server.Implementations.Tests create mode 100644 tests/Jellyfin.Server.Implementations.Tests/bin/Debug/net10.0/CoverletSourceRootsMapping_Jellyfin.Server.Implementations.Tests create mode 100644 tests/Jellyfin.Server.Implementations.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/obj/Debug/net10.0/Jellyfin.Server.Implementations.Tests.AssemblyInfo.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/obj/Debug/net10.0/Jellyfin.Server.Implementations.Tests.AssemblyInfoInputs.cache create mode 100644 tests/Jellyfin.Server.Implementations.Tests/obj/Debug/net10.0/Jellyfin.Server.Implementations.Tests.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 tests/Jellyfin.Server.Implementations.Tests/obj/Debug/net10.0/Jellyfin.Server.Implementations.Tests.assets.cache create mode 100644 tests/Jellyfin.Server.Implementations.Tests/obj/Debug/net10.0/Jellyfin.Server.Implementations.Tests.csproj.AssemblyReference.cache create mode 100644 tests/Jellyfin.Server.Implementations.Tests/obj/Jellyfin.Server.Implementations.Tests.csproj.nuget.dgspec.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/obj/Jellyfin.Server.Implementations.Tests.csproj.nuget.g.props create mode 100644 tests/Jellyfin.Server.Implementations.Tests/obj/Jellyfin.Server.Implementations.Tests.csproj.nuget.g.targets create mode 100644 tests/Jellyfin.Server.Implementations.Tests/obj/project.assets.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/obj/project.nuget.cache create mode 100644 tests/Jellyfin.Server.Implementations.Tests/obj/project.packagespec.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/obj/rider.project.model.nuget.info create mode 100644 tests/Jellyfin.Server.Implementations.Tests/obj/rider.project.restore.info create mode 100644 tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/BaseJellyfinTestController.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/EncoderController.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/LiveTvControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/MediaInfoControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/MusicGenreControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/PersonsControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/PluginsControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/VideosControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj create mode 100644 tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Middleware/RobotsRedirectionMiddlewareTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Test Data/dummy.m3u8 create mode 100644 tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/TestPage.html create mode 100644 tests/Jellyfin.Server.Integration.Tests/TestPlugin.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/TestPluginWithoutPages.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/WebSocketTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping_Jellyfin.Server.Integration.Tests create mode 100644 tests/Jellyfin.Server.Integration.Tests/bin/Debug/net10.0/CoverletSourceRootsMapping_Jellyfin.Server.Integration.Tests create mode 100644 tests/Jellyfin.Server.Integration.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/obj/Debug/net10.0/Jellyfin.Server.Integration.Tests.AssemblyInfo.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/obj/Debug/net10.0/Jellyfin.Server.Integration.Tests.AssemblyInfoInputs.cache create mode 100644 tests/Jellyfin.Server.Integration.Tests/obj/Debug/net10.0/Jellyfin.Server.Integration.Tests.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 tests/Jellyfin.Server.Integration.Tests/obj/Debug/net10.0/Jellyfin.Server.Integration.Tests.assets.cache create mode 100644 tests/Jellyfin.Server.Integration.Tests/obj/Debug/net10.0/Jellyfin.Server.Integration.Tests.csproj.AssemblyReference.cache create mode 100644 tests/Jellyfin.Server.Integration.Tests/obj/Jellyfin.Server.Integration.Tests.csproj.nuget.dgspec.json create mode 100644 tests/Jellyfin.Server.Integration.Tests/obj/Jellyfin.Server.Integration.Tests.csproj.nuget.g.props create mode 100644 tests/Jellyfin.Server.Integration.Tests/obj/Jellyfin.Server.Integration.Tests.csproj.nuget.g.targets create mode 100644 tests/Jellyfin.Server.Integration.Tests/obj/project.assets.json create mode 100644 tests/Jellyfin.Server.Integration.Tests/obj/project.nuget.cache create mode 100644 tests/Jellyfin.Server.Integration.Tests/obj/project.packagespec.json create mode 100644 tests/Jellyfin.Server.Integration.Tests/obj/rider.project.model.nuget.info create mode 100644 tests/Jellyfin.Server.Integration.Tests/obj/rider.project.restore.info create mode 100644 tests/Jellyfin.Server.Integration.Tests/xunit.runner.json create mode 100644 tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj create mode 100644 tests/Jellyfin.Server.Tests/ParseNetworkTests.cs create mode 100644 tests/Jellyfin.Server.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping_Jellyfin.Server.Tests create mode 100644 tests/Jellyfin.Server.Tests/bin/Debug/net10.0/CoverletSourceRootsMapping_Jellyfin.Server.Tests create mode 100644 tests/Jellyfin.Server.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 tests/Jellyfin.Server.Tests/obj/Debug/net10.0/Jellyfin.Server.Tests.AssemblyInfo.cs create mode 100644 tests/Jellyfin.Server.Tests/obj/Debug/net10.0/Jellyfin.Server.Tests.AssemblyInfoInputs.cache create mode 100644 tests/Jellyfin.Server.Tests/obj/Debug/net10.0/Jellyfin.Server.Tests.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 tests/Jellyfin.Server.Tests/obj/Debug/net10.0/Jellyfin.Server.Tests.assets.cache create mode 100644 tests/Jellyfin.Server.Tests/obj/Debug/net10.0/Jellyfin.Server.Tests.csproj.AssemblyReference.cache create mode 100644 tests/Jellyfin.Server.Tests/obj/Jellyfin.Server.Tests.csproj.nuget.dgspec.json create mode 100644 tests/Jellyfin.Server.Tests/obj/Jellyfin.Server.Tests.csproj.nuget.g.props create mode 100644 tests/Jellyfin.Server.Tests/obj/Jellyfin.Server.Tests.csproj.nuget.g.targets create mode 100644 tests/Jellyfin.Server.Tests/obj/project.assets.json create mode 100644 tests/Jellyfin.Server.Tests/obj/project.nuget.cache create mode 100644 tests/Jellyfin.Server.Tests/obj/project.packagespec.json create mode 100644 tests/Jellyfin.Server.Tests/obj/rider.project.model.nuget.info create mode 100644 tests/Jellyfin.Server.Tests/obj/rider.project.restore.info create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Location/MovieNfoLocationTests.cs create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicVideoNfoParserTests.cs create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Test Data/American Gods.nfo create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Test Data/Dancing Queen.nfo create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Test Data/Fanart.nfo create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Test Data/Imdb.nfo create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Test Data/Lilo & Stitch.nfo create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Test Data/Radarr.nfo create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Test Data/Rising.nfo create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Test Data/Season 01.nfo create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Test Data/Sonarr-Thumb.nfo create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Test Data/Stargate Atlantis S01E01-E04.nfo create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Best of 1980-1990.nfo create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Bone Orchard.nfo create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Test Data/Tmdb.nfo create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Test Data/Tvdb.nfo create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Test Data/U2.nfo create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping_Jellyfin.XbmcMetadata.Tests create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/bin/Debug/net10.0/CoverletSourceRootsMapping_Jellyfin.XbmcMetadata.Tests create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/obj/Debug/net10.0/Jellyfin.XbmcMetadata.Tests.AssemblyInfo.cs create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/obj/Debug/net10.0/Jellyfin.XbmcMetadata.Tests.AssemblyInfoInputs.cache create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/obj/Debug/net10.0/Jellyfin.XbmcMetadata.Tests.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/obj/Debug/net10.0/Jellyfin.XbmcMetadata.Tests.assets.cache create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/obj/Debug/net10.0/Jellyfin.XbmcMetadata.Tests.csproj.AssemblyReference.cache create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/obj/Jellyfin.XbmcMetadata.Tests.csproj.nuget.dgspec.json create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/obj/Jellyfin.XbmcMetadata.Tests.csproj.nuget.g.props create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/obj/Jellyfin.XbmcMetadata.Tests.csproj.nuget.g.targets create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/obj/project.assets.json create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/obj/project.nuget.cache create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/obj/project.packagespec.json create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/obj/rider.project.model.nuget.info create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/obj/rider.project.restore.info create mode 100644 tests/coverletArgs.runsettings diff --git a/.idea/.idea.Jellyfin/.idea/encodings.xml b/.idea/.idea.Jellyfin/.idea/encodings.xml new file mode 100644 index 00000000..df87cf95 --- /dev/null +++ b/.idea/.idea.Jellyfin/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/BannedSymbols.txt b/BannedSymbols.txt new file mode 100644 index 00000000..875f7215 --- /dev/null +++ b/BannedSymbols.txt @@ -0,0 +1,4 @@ +P:System.Threading.Tasks.Task`1.Result +M:System.Guid.op_Equality(System.Guid,System.Guid) +M:System.Guid.op_Inequality(System.Guid,System.Guid) +M:System.Guid.Equals(System.Object) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 00000000..cb7d3fbb --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,290 @@ +# Jellyfin Contributors + + - [1337joe](https://github.com/1337joe) + - [97carmine](https://github.com/97carmine) + - [Abbe98](https://github.com/Abbe98) + - [agrenott](https://github.com/agrenott) + - [alltilla](https://github.com/alltilla) + - [AndreCarvalho](https://github.com/AndreCarvalho) + - [anthonylavado](https://github.com/anthonylavado) + - [Artiume](https://github.com/Artiume) + - [AThomsen](https://github.com/AThomsen) + - [barongreenback](https://github.com/BaronGreenback) + - [barronpm](https://github.com/barronpm) + - [bilde2910](https://github.com/bilde2910) + - [bfayers](https://github.com/bfayers) + - [BnMcG](https://github.com/BnMcG) + - [Bond-009](https://github.com/Bond-009) + - [brianjmurrell](https://github.com/brianjmurrell) + - [bugfixin](https://github.com/bugfixin) + - [chaosinnovator](https://github.com/chaosinnovator) + - [ckcr4lyf](https://github.com/ckcr4lyf) + - [cocool97](https://github.com/cocool97) + - [ConfusedPolarBear](https://github.com/ConfusedPolarBear) + - [crankdoofus](https://github.com/crankdoofus) + - [crobibero](https://github.com/crobibero) + - [cromefire](https://github.com/cromefire) + - [cryptobank](https://github.com/cryptobank) + - [cvium](https://github.com/cvium) + - [dannymichel](https://github.com/dannymichel) + - [darioackermann](https://github.com/darioackermann) + - [DaveChild](https://github.com/DaveChild) + - [DavidFair](https://github.com/DavidFair) + - [Delgan](https://github.com/Delgan) + - [Derpipose](https://github.com/Derpipose) + - [dcrdev](https://github.com/dcrdev) + - [dhartung](https://github.com/dhartung) + - [dinki](https://github.com/dinki) + - [dkanada](https://github.com/dkanada) + - [dlahoti](https://github.com/dlahoti) + - [dmitrylyzo](https://github.com/dmitrylyzo) + - [DMouse10462](https://github.com/DMouse10462) + - [DrPandemic](https://github.com/DrPandemic) + - [eglia](https://github.com/eglia) + - [EgorBakanov](https://github.com/EgorBakanov) + - [EraYaN](https://github.com/EraYaN) + - [escabe](https://github.com/escabe) + - [excelite](https://github.com/excelite) + - [fasheng](https://github.com/fasheng) + - [ferferga](https://github.com/ferferga) + - [fhriley](https://github.com/fhriley) + - [flemse](https://github.com/flemse) + - [Froghut](https://github.com/Froghut) + - [fruhnow](https://github.com/fruhnow) + - [geilername](https://github.com/geilername) + - [GermanCoding](https://github.com/GermanCoding) + - [gnattu](https://github.com/gnattu) + - [GodTamIt](https://github.com/GodTamIt) + - [grafixeyehero](https://github.com/grafixeyehero) + - [h1nk](https://github.com/h1nk) + - [hawken93](https://github.com/hawken93) + - [HelloWorld017](https://github.com/HelloWorld017) + - [ikomhoog](https://github.com/ikomhoog) + - [iwalton3](https://github.com/iwalton3) + - [jftuga](https://github.com/jftuga) + - [jkhsjdhjs](https://github.com/jkhsjdhjs) + - [jmshrv](https://github.com/jmshrv) + - [joern-h](https://github.com/joern-h) + - [joshuaboniface](https://github.com/joshuaboniface) + - [JustAMan](https://github.com/JustAMan) + - [justinfenn](https://github.com/justinfenn) + - [JPVenson](https://github.com/JPVenson) + - [KerryRJ](https://github.com/KerryRJ) + - [Larvitar](https://github.com/Larvitar) + - [LeoVerto](https://github.com/LeoVerto) + - [Liggy](https://github.com/Liggy) + - [lmaonator](https://github.com/lmaonator) + - [LogicalPhallacy](https://github.com/LogicalPhallacy) + - [loli10K](https://github.com/loli10K) + - [lostmypillow](https://github.com/lostmypillow) + - [Lynxy](https://github.com/Lynxy) + - [ManfredRichthofen](https://github.com/ManfredRichthofen) + - [Marenz](https://github.com/Marenz) + - [marius-luca-87](https://github.com/marius-luca-87) + - [mark-monteiro](https://github.com/mark-monteiro) + - [MarkCiliaVincenti](https://github.com/MarkCiliaVincenti) + - [Matt07211](https://github.com/Matt07211) + - [Maxr1998](https://github.com/Maxr1998) + - [mcarlton00](https://github.com/mcarlton00) + - [mitchfizz05](https://github.com/mitchfizz05) + - [mohd-akram](https://github.com/mohd-akram) + - [MrTimscampi](https://github.com/MrTimscampi) + - [n8225](https://github.com/n8225) + - [Nalsai](https://github.com/Nalsai) + - [Narfinger](https://github.com/Narfinger) + - [NathanPickard](https://github.com/NathanPickard) + - [neilsb](https://github.com/neilsb) + - [nevado](https://github.com/nevado) + - [Nickbert7](https://github.com/Nickbert7) + - [nicknsy](https://github.com/nicknsy) + - [nvllsvm](https://github.com/nvllsvm) + - [nyanmisaka](https://github.com/nyanmisaka) + - [OancaAndrei](https://github.com/OancaAndrei) + - [obradovichv](https://github.com/obradovichv) + - [oddstr13](https://github.com/oddstr13) + - [orryverducci](https://github.com/orryverducci) + - [petermcneil](https://github.com/petermcneil) + - [Phlogi](https://github.com/Phlogi) + - [pjeanjean](https://github.com/pjeanjean) + - [ploughpuff](https://github.com/ploughpuff) + - [pR0Ps](https://github.com/pR0Ps) + - [PrplHaz4](https://github.com/PrplHaz4) + - [RazeLighter777](https://github.com/RazeLighter777) + - [redSpoutnik](https://github.com/redSpoutnik) + - [ringmatter](https://github.com/ringmatter) + - [ryan-hartzell](https://github.com/ryan-hartzell) + - [s0urcelab](https://github.com/s0urcelab) + - [sachk](https://github.com/sachk) + - [sammyrc34](https://github.com/sammyrc34) + - [samuel9554](https://github.com/samuel9554) + - [SapientGuardian](https://github.com/SapientGuardian) + - [scheidleon](https://github.com/scheidleon) + - [sebPomme](https://github.com/sebPomme) + - [SegiH](https://github.com/SegiH) + - [SenorSmartyPants](https://github.com/SenorSmartyPants) + - [shemanaev](https://github.com/shemanaev) + - [skaro13](https://github.com/skaro13) + - [sl1288](https://github.com/sl1288) + - [Smith00101010](https://github.com/Smith00101010) + - [sorinyo2004](https://github.com/sorinyo2004) + - [sparky8251](https://github.com/sparky8251) + - [spookbits](https://github.com/spookbits) + - [ssenart](https://github.com/ssenart) + - [stanionascu](https://github.com/stanionascu) + - [stevehayles](https://github.com/stevehayles) + - [StollD](https://github.com/StollD) + - [SuperSandro2000](https://github.com/SuperSandro2000) + - [tbraeutigam](https://github.com/tbraeutigam) + - [teacupx](https://github.com/teacupx) + - [TelepathicWalrus](https://github.com/TelepathicWalrus) + - [Terror-Gene](https://github.com/Terror-Gene) + - [ThatNerdyPikachu](https://github.com/ThatNerdyPikachu) + - [ThibaultNocchi](https://github.com/ThibaultNocchi) + - [thornbill](https://github.com/thornbill) + - [ThreeFive-O](https://github.com/ThreeFive-O) + - [tjwalkr3](https://github.com/tjwalkr3) + - [TrisMcC](https://github.com/TrisMcC) + - [trumblejoe](https://github.com/trumblejoe) + - [TtheCreator](https://github.com/TtheCreator) + - [twinkybot](https://github.com/twinkybot) + - [Ullmie02](https://github.com/Ullmie02) + - [Unhelpful](https://github.com/Unhelpful) + - [viaregio](https://github.com/viaregio) + - [vitorsemeano](https://github.com/vitorsemeano) + - [voodoos](https://github.com/voodoos) + - [whooo](https://github.com/whooo) + - [WiiPlayer2](https://github.com/WiiPlayer2) + - [WillWill56](https://github.com/WillWill56) + - [wtayl0r](https://github.com/wtayl0r) + - [Wuerfelbecher](https://github.com/Wuerfelbecher) + - [Wunax](https://github.com/Wunax) + - [WWWesten](https://github.com/WWWesten) + - [WX9yMOXWId](https://github.com/WX9yMOXWId) + - [xosdy](https://github.com/xosdy) + - [XVicarious](https://github.com/XVicarious) + - [YouKnowBlom](https://github.com/YouKnowBlom) + - [ZachPhelan](https://github.com/ZachPhelan) + - [KristupasSavickas](https://github.com/KristupasSavickas) + - [Pusta](https://github.com/pusta) + - [nielsvanvelzen](https://github.com/nielsvanvelzen) + - [skyfrk](https://github.com/skyfrk) + - [ianjazz246](https://github.com/ianjazz246) + - [peterspenler](https://github.com/peterspenler) + - [MBR-0001](https://github.com/MBR-0001) + - [jonas-resch](https://github.com/jonas-resch) + - [vgambier](https://github.com/vgambier) + - [MinecraftPlaye](https://github.com/MinecraftPlaye) + - [RealGreenDragon](https://github.com/RealGreenDragon) + - [ipitio](https://github.com/ipitio) + - [TheTyrius](https://github.com/TheTyrius) + - [tallbl0nde](https://github.com/tallbl0nde) + - [sleepycatcoding](https://github.com/sleepycatcoding) + - [scampower3](https://github.com/scampower3) + - [Chris-Codes-It](https://github.com/Chris-Codes-It) + - [Pithaya](https://github.com/Pithaya) + - [Çağrı Sakaoğlu](https://github.com/ilovepilav) + - [Barasingha](https://github.com/MaVdbussche) + - [Gauvino](https://github.com/Gauvino) + - [felix920506](https://github.com/felix920506) + - [btopherjohnson](https://github.com/btopherjohnson) + - [GeorgeH005](https://github.com/GeorgeH005) + - [Vedant](https://github.com/viktory36/) + - [NotSaifA](https://github.com/NotSaifA) + - [HonestlyWhoKnows](https://github.com/honestlywhoknows) + - [TheMelmacian](https://github.com/TheMelmacian) + - [ItsAllAboutTheCode](https://github.com/ItsAllAboutTheCode) + - [pret0rian8](https://github.com/pret0rian) + - [jaina heartles](https://github.com/heartles) + - [oxixes](https://github.com/oxixes) + - [elfalem](https://github.com/elfalem) + - [Kenneth Cochran](https://github.com/kennethcochran) + - [benedikt257](https://github.com/benedikt257) + - [revam](https://github.com/revam) + - [allesmi](https://github.com/allesmi) + - [ThunderClapLP](https://github.com/ThunderClapLP) + - [Shoham Peller](https://github.com/spellr) + - [theshoeshiner](https://github.com/theshoeshiner) + - [TokerX](https://github.com/TokerX) + - [GeneMarks](https://github.com/GeneMarks) + - [Kirill Nikiforov](https://github.com/allmazz) + - [bjorntp](https://github.com/bjorntp) + - [martenumberto](https://github.com/martenumberto) + - [ZeusCraft10](https://github.com/ZeusCraft10) + - [MarcoCoreDuo](https://github.com/MarcoCoreDuo) + +# Emby Contributors + + - [LukePulverenti](https://github.com/LukePulverenti) + - [ebr11](https://github.com/ebr11) + - [lalmanzar](https://github.com/lalmanzar) + - [schneifu](https://github.com/schneifu) + - [Mark2xv](https://github.com/Mark2xv) + - [ScottRapsey](https://github.com/ScottRapsey) + - [skynet600](https://github.com/skynet600) + - [Cheesegeezer](https://githum.com/Cheesegeezer) + - [Radeon](https://github.com/radeonorama) + - [gcw07](https://github.com/gcw07) + - [SivaramAdhiappan](https://github.com/shivaram1190) + - [CWatkinsNash](https://github.com/CWatkinsNash) + - [sfnetwork](https://github.com/sfnetwork) + - [Logos302](https://github.com/Logos302) + - [TheWorkz](https://github.com/TheWorkz) + - [mboehler](https://github.com/mboehler) + - [KaHooli](https://github.com/KaHooli) + - [xzener](https://github.com/xzener) + - [CBers](https://github.com/CBers) + - [Sagaia](https://github.com/Sagaia) + - [JHawk111](https://github.com/JHawk111) + - [David3663](https://github.com/david3663) + - [Smyken](https://github.com/Smyken) + - [doron1](https://github.com/doron1) + - [brainfryd](https://github.com/brainfryd) + - [DGMayor](http://github.com/DGMayor) + - [Jon-theHTPC](https://github.com/Jon-theHTPC) + - [aspdend](https://github.com/aspdend) + - [RedshirtMB](https://github.com/RedshirtMB) + - [thealienamongus](https://github.com/thealienamongus) + - [brocass](https://github.com/brocass) + - [pjrollo2000](https://github.com/pjrollo2000) + - [abobader](https://github.com/abobader) + - [milli260876](https://github.com/milli260876) + - [vileboy](https://github.com/vileboy) + - [starkadius](https://github.com/starkadius) + - [wraslor](https://github.com/wraslor) + - [mrwebsmith](https://github.com/mrwebsmith) + - [rickster53](https://github.com/rickster53) + - [Tharnax](https://github.com/Tharnax) + - [0sm0](https://github.com/0sm0) + - [swhitmore](https://github.com/swhitmore) + - [DigiTM](https://github.com/DigiTM) + - [crisliv / xliv](https://github.com/crisliv) + - [Yogi](https://github.com/yogi12) + - [madFloyd](https://github.com/madFloyd) + - [yardameus](https://github.com/yardameus) + - [rrb008](https://github.com/rrb008) + - [Toonguy](https://github.com/Toonguy) + - [Alwin Hummels](https://github.com/AlwinHummels) + - [trooper11](https://github.com/trooper11) + - [danlotfy](https://github.com/danlotfy) + - [jordy1955](https://github.com/jordy1955) + - [JoshFink](https://github.com/JoshFink) + - [Detector1](https://github.com/Detector1) + - [BlackIce013](https://github.com/blackice013) + - [mporcas](https://github.com/mporcas) + - [tikuf](https://github.com/tikuf/) + - [Tim Hobbs](https://github.com/timhobbs) + - [SvenVandenbrande](https://github.com/SvenVandenbrande) + - [olsh](https://github.com/olsh) + - [lbenini](https://github.com/lbenini) + - [gnuyent](https://github.com/gnuyent) + - [Matthew Jones](https://github.com/matthew-jones-uk) + - [Jakob Kukla](https://github.com/jakobkukla) + - [Utku Özdemir](https://github.com/utkuozdemir) + - [JPUC1143](https://github.com/Jpuc1143/) + - [0x25CBFC4F](https://github.com/0x25CBFC4F) + - [Robert Lützner](https://github.com/rluetzner) + - [Nathan McCrina](https://github.com/nfmccrina) + - [Martin Reuter](https://github.com/reuterma24) + - [Michael McElroy](https://github.com/mcmcelro) + - [Soumyadip Auddy](https://github.com/SoumyadipAuddy) + - [DerMaddis](https://github.com/dermaddis) diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..8400f4c5 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,27 @@ + + + + + enable + + + + true + NU1902;NU1903 + + + + AllEnabledByDefault + + + + + + + + + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 00000000..0cb01723 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,90 @@ + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Emby.Naming/Audio/AlbumParser.cs b/Emby.Naming/Audio/AlbumParser.cs new file mode 100644 index 00000000..97961778 --- /dev/null +++ b/Emby.Naming/Audio/AlbumParser.cs @@ -0,0 +1,70 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text.RegularExpressions; +using Emby.Naming.Common; +using Jellyfin.Extensions; + +namespace Emby.Naming.Audio +{ + /// + /// Helper class to determine if Album is multipart. + /// + public partial class AlbumParser + { + private readonly NamingOptions _options; + + /// + /// Initializes a new instance of the class. + /// + /// Naming options containing AlbumStackingPrefixes. + public AlbumParser(NamingOptions options) + { + _options = options; + } + + [GeneratedRegex(@"[-\.\(\)\s]+")] + private static partial Regex CleanRegex(); + + /// + /// Function that determines if album is multipart. + /// + /// Path to file. + /// True if album is multipart. + public bool IsMultiPart(string path) + { + var filename = Path.GetFileName(path); + if (filename.Length == 0) + { + return false; + } + + // TODO: Move this logic into options object + // Even better, remove the prefixes and come up with regexes + // But Kodi documentation seems to be weak for audio + + // Normalize + // Remove whitespace + filename = CleanRegex().Replace(filename, " "); + + ReadOnlySpan trimmedFilename = filename.AsSpan().TrimStart(); + + foreach (var prefix in _options.AlbumStackingPrefixes) + { + if (!trimmedFilename.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + var tmp = trimmedFilename.Slice(prefix.Length).Trim(); + + if (int.TryParse(tmp.LeftPart(' '), CultureInfo.InvariantCulture, out _)) + { + return true; + } + } + + return false; + } + } +} diff --git a/Emby.Naming/Audio/AudioFileParser.cs b/Emby.Naming/Audio/AudioFileParser.cs new file mode 100644 index 00000000..2b610ec7 --- /dev/null +++ b/Emby.Naming/Audio/AudioFileParser.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; +using Emby.Naming.Common; +using Jellyfin.Extensions; + +namespace Emby.Naming.Audio +{ + /// + /// Static helper class to determine if file at path is audio file. + /// + public static class AudioFileParser + { + /// + /// Static helper method to determine if file at path is audio file. + /// + /// Path to file. + /// containing AudioFileExtensions. + /// True if file at path is audio file. + public static bool IsAudioFile(string path, NamingOptions options) + { + var extension = Path.GetExtension(path.AsSpan()); + return options.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/Emby.Naming/AudioBook/AudioBookFileInfo.cs b/Emby.Naming/AudioBook/AudioBookFileInfo.cs new file mode 100644 index 00000000..862e3966 --- /dev/null +++ b/Emby.Naming/AudioBook/AudioBookFileInfo.cs @@ -0,0 +1,77 @@ +using System; + +namespace Emby.Naming.AudioBook +{ + /// + /// Represents a single video file. + /// + public class AudioBookFileInfo : IComparable + { + /// + /// Initializes a new instance of the class. + /// + /// Path to audiobook file. + /// File type. + /// Number of part this file represents. + /// Number of chapter this file represents. + public AudioBookFileInfo(string path, string container, int? partNumber = default, int? chapterNumber = default) + { + Path = path; + Container = container; + PartNumber = partNumber; + ChapterNumber = chapterNumber; + } + + /// + /// Gets or sets the path. + /// + /// The path. + public string Path { get; set; } + + /// + /// Gets or sets the container. + /// + /// The container. + public string Container { get; set; } + + /// + /// Gets or sets the part number. + /// + /// The part number. + public int? PartNumber { get; set; } + + /// + /// Gets or sets the chapter number. + /// + /// The chapter number. + public int? ChapterNumber { get; set; } + + /// + public int CompareTo(AudioBookFileInfo? other) + { + if (ReferenceEquals(this, other)) + { + return 0; + } + + if (ReferenceEquals(null, other)) + { + return 1; + } + + var chapterNumberComparison = Nullable.Compare(ChapterNumber, other.ChapterNumber); + if (chapterNumberComparison != 0) + { + return chapterNumberComparison; + } + + var partNumberComparison = Nullable.Compare(PartNumber, other.PartNumber); + if (partNumberComparison != 0) + { + return partNumberComparison; + } + + return string.Compare(Path, other.Path, StringComparison.Ordinal); + } + } +} diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs new file mode 100644 index 00000000..75fdedfe --- /dev/null +++ b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs @@ -0,0 +1,67 @@ +using System.Globalization; +using System.IO; +using System.Text.RegularExpressions; +using Emby.Naming.Common; + +namespace Emby.Naming.AudioBook +{ + /// + /// Parser class to extract part and/or chapter number from audiobook filename. + /// + public class AudioBookFilePathParser + { + private readonly NamingOptions _options; + + /// + /// Initializes a new instance of the class. + /// + /// Naming options containing AudioBookPartsExpressions. + public AudioBookFilePathParser(NamingOptions options) + { + _options = options; + } + + /// + /// Based on regex determines if filename includes part/chapter number. + /// + /// Path to audiobook file. + /// Returns object. + public AudioBookFilePathParserResult Parse(string path) + { + AudioBookFilePathParserResult result = default; + var fileName = Path.GetFileNameWithoutExtension(path); + foreach (var expression in _options.AudioBookPartsExpressions) + { + var match = Regex.Match(fileName, expression, RegexOptions.IgnoreCase); + if (match.Success) + { + if (!result.ChapterNumber.HasValue) + { + var value = match.Groups["chapter"]; + if (value.Success) + { + if (int.TryParse(value.ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue)) + { + result.ChapterNumber = intValue; + } + } + } + + if (!result.PartNumber.HasValue) + { + var value = match.Groups["part"]; + if (value.Success) + { + if (int.TryParse(value.ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue)) + { + result.PartNumber = intValue; + } + } + } + } + } + + return result; + } + } +} diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs new file mode 100644 index 00000000..ae8c8a39 --- /dev/null +++ b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs @@ -0,0 +1,18 @@ +namespace Emby.Naming.AudioBook +{ + /// + /// Data object for passing result of audiobook part/chapter extraction. + /// + public record struct AudioBookFilePathParserResult + { + /// + /// Gets or sets optional number of path extracted from audiobook filename. + /// + public int? PartNumber { get; set; } + + /// + /// Gets or sets optional number of chapter extracted from audiobook filename. + /// + public int? ChapterNumber { get; set; } + } +} diff --git a/Emby.Naming/AudioBook/AudioBookInfo.cs b/Emby.Naming/AudioBook/AudioBookInfo.cs new file mode 100644 index 00000000..acd8905a --- /dev/null +++ b/Emby.Naming/AudioBook/AudioBookInfo.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; + +namespace Emby.Naming.AudioBook +{ + /// + /// Represents a complete video, including all parts and subtitles. + /// + public class AudioBookInfo + { + /// + /// Initializes a new instance of the class. + /// + /// Name of audiobook. + /// Year of audiobook release. + /// List of files composing the actual audiobook. + /// List of extra files. + /// Alternative version of files. + public AudioBookInfo(string name, int? year, IReadOnlyList files, IReadOnlyList extras, IReadOnlyList alternateVersions) + { + Name = name; + Year = year; + Files = files; + Extras = extras; + AlternateVersions = alternateVersions; + } + + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + + /// + /// Gets or sets the year. + /// + public int? Year { get; set; } + + /// + /// Gets or sets the files. + /// + /// The files. + public IReadOnlyList Files { get; set; } + + /// + /// Gets or sets the extras. + /// + /// The extras. + public IReadOnlyList Extras { get; set; } + + /// + /// Gets or sets the alternate versions. + /// + /// The alternate versions. + public IReadOnlyList AlternateVersions { get; set; } + } +} diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs new file mode 100644 index 00000000..ca304102 --- /dev/null +++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Emby.Naming.Common; +using Emby.Naming.Video; +using MediaBrowser.Model.IO; + +namespace Emby.Naming.AudioBook +{ + /// + /// Class used to resolve Name, Year, alternative files and extras from stack of files. + /// + public class AudioBookListResolver + { + private readonly NamingOptions _options; + private readonly AudioBookResolver _audioBookResolver; + + /// + /// Initializes a new instance of the class. + /// + /// Naming options passed along to and . + public AudioBookListResolver(NamingOptions options) + { + _options = options; + _audioBookResolver = new AudioBookResolver(_options); + } + + /// + /// Resolves Name, Year and differentiate alternative files and extras from regular audiobook files. + /// + /// List of files related to audiobook. + /// Returns IEnumerable of . + public IEnumerable Resolve(IEnumerable files) + { + // File with empty fullname will be sorted out here. + var audiobookFileInfos = files + .Select(i => _audioBookResolver.Resolve(i.FullName)) + .OfType(); + + var stackResult = StackResolver.ResolveAudioBooks(audiobookFileInfos); + + foreach (var stack in stackResult) + { + var stackFiles = stack.Files + .Select(i => _audioBookResolver.Resolve(i)) + .OfType() + .ToList(); + + stackFiles.Sort(); + + var nameParserResult = new AudioBookNameParser(_options).Parse(stack.Name); + + FindExtraAndAlternativeFiles(ref stackFiles, out var extras, out var alternativeVersions, nameParserResult); + + var info = new AudioBookInfo( + nameParserResult.Name, + nameParserResult.Year, + stackFiles, + extras, + alternativeVersions); + + yield return info; + } + } + + private void FindExtraAndAlternativeFiles(ref List stackFiles, out List extras, out List alternativeVersions, AudioBookNameParserResult nameParserResult) + { + extras = new List(); + alternativeVersions = new List(); + + var haveChaptersOrPages = stackFiles.Any(x => x.ChapterNumber is not null || x.PartNumber is not null); + var groupedBy = stackFiles.GroupBy(file => new { file.ChapterNumber, file.PartNumber }); + var nameWithReplacedDots = nameParserResult.Name.Replace(' ', '.'); + + foreach (var group in groupedBy) + { + if (group.Key.ChapterNumber is null && group.Key.PartNumber is null) + { + if (group.Count() > 1 || haveChaptersOrPages) + { + List? ex = null; + List? alt = null; + + foreach (var audioFile in group) + { + var name = Path.GetFileNameWithoutExtension(audioFile.Path.AsSpan()); + if (name.Equals("audiobook", StringComparison.OrdinalIgnoreCase) + || name.Contains(nameParserResult.Name, StringComparison.OrdinalIgnoreCase) + || name.Contains(nameWithReplacedDots, StringComparison.OrdinalIgnoreCase)) + { + (alt ??= new()).Add(audioFile); + } + else + { + (ex ??= new()).Add(audioFile); + } + } + + if (ex is not null) + { + var extra = ex + .OrderBy(x => x.Container) + .ThenBy(x => x.Path) + .ToList(); + + stackFiles = stackFiles.Except(extra).ToList(); + extras.AddRange(extra); + } + + if (alt is not null) + { + var alternatives = alt + .OrderBy(x => x.Container) + .ThenBy(x => x.Path) + .ToList(); + + var main = FindMainAudioBookFile(alternatives, nameParserResult.Name); + alternatives.Remove(main); + stackFiles = stackFiles.Except(alternatives).ToList(); + alternativeVersions.AddRange(alternatives); + } + } + } + else if (group.Count() > 1) + { + var alternatives = group + .OrderBy(x => x.Container) + .ThenBy(x => x.Path) + .Skip(1) + .ToList(); + + stackFiles = stackFiles.Except(alternatives).ToList(); + alternativeVersions.AddRange(alternatives); + } + } + } + + private AudioBookFileInfo FindMainAudioBookFile(List files, string name) + { + var main = files.Find(x => Path.GetFileNameWithoutExtension(x.Path).Equals(name, StringComparison.OrdinalIgnoreCase)); + main ??= files.FirstOrDefault(x => Path.GetFileNameWithoutExtension(x.Path).Equals("audiobook", StringComparison.OrdinalIgnoreCase)); + main ??= files.OrderBy(x => x.Container) + .ThenBy(x => x.Path) + .First(); + + return main; + } + } +} diff --git a/Emby.Naming/AudioBook/AudioBookNameParser.cs b/Emby.Naming/AudioBook/AudioBookNameParser.cs new file mode 100644 index 00000000..5ea649db --- /dev/null +++ b/Emby.Naming/AudioBook/AudioBookNameParser.cs @@ -0,0 +1,67 @@ +using System.Globalization; +using System.Text.RegularExpressions; +using Emby.Naming.Common; + +namespace Emby.Naming.AudioBook +{ + /// + /// Helper class to retrieve name and year from audiobook previously retrieved name. + /// + public class AudioBookNameParser + { + private readonly NamingOptions _options; + + /// + /// Initializes a new instance of the class. + /// + /// Naming options containing AudioBookNamesExpressions. + public AudioBookNameParser(NamingOptions options) + { + _options = options; + } + + /// + /// Parse name and year from previously determined name of audiobook. + /// + /// Name of the audiobook. + /// Returns object. + public AudioBookNameParserResult Parse(string name) + { + AudioBookNameParserResult result = default; + foreach (var expression in _options.AudioBookNamesExpressions) + { + var match = Regex.Match(name, expression, RegexOptions.IgnoreCase); + if (match.Success) + { + if (result.Name is null) + { + var value = match.Groups["name"]; + if (value.Success) + { + result.Name = value.Value; + } + } + + if (!result.Year.HasValue) + { + var value = match.Groups["year"]; + if (value.Success) + { + if (int.TryParse(value.ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue)) + { + result.Year = intValue; + } + } + } + } + } + + if (string.IsNullOrEmpty(result.Name)) + { + result.Name = name; + } + + return result; + } + } +} diff --git a/Emby.Naming/AudioBook/AudioBookNameParserResult.cs b/Emby.Naming/AudioBook/AudioBookNameParserResult.cs new file mode 100644 index 00000000..3f2d7b2b --- /dev/null +++ b/Emby.Naming/AudioBook/AudioBookNameParserResult.cs @@ -0,0 +1,18 @@ +namespace Emby.Naming.AudioBook +{ + /// + /// Data object used to pass result of name and year parsing. + /// + public struct AudioBookNameParserResult + { + /// + /// Gets or sets name of audiobook. + /// + public string Name { get; set; } + + /// + /// Gets or sets optional year of release. + /// + public int? Year { get; set; } + } +} diff --git a/Emby.Naming/AudioBook/AudioBookResolver.cs b/Emby.Naming/AudioBook/AudioBookResolver.cs new file mode 100644 index 00000000..183b6c3b --- /dev/null +++ b/Emby.Naming/AudioBook/AudioBookResolver.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using Emby.Naming.Common; +using Jellyfin.Extensions; + +namespace Emby.Naming.AudioBook +{ + /// + /// Resolve specifics (path, container, partNumber, chapterNumber) about audiobook file. + /// + public class AudioBookResolver + { + private readonly NamingOptions _options; + + /// + /// Initializes a new instance of the class. + /// + /// containing AudioFileExtensions and also used to pass to AudioBookFilePathParser. + public AudioBookResolver(NamingOptions options) + { + _options = options; + } + + /// + /// Resolve specifics (path, container, partNumber, chapterNumber) about audiobook file. + /// + /// Path to audiobook file. + /// Returns object. + public AudioBookFileInfo? Resolve(string path) + { + if (path.Length == 0 || Path.GetFileNameWithoutExtension(path).Length == 0) + { + // Return null to indicate this path will not be used, instead of stopping whole process with exception + return null; + } + + var extension = Path.GetExtension(path); + + // Check supported extensions + if (!_options.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + var container = extension.TrimStart('.'); + + var parsingResult = new AudioBookFilePathParser(_options).Parse(path); + + return new AudioBookFileInfo( + path, + container, + chapterNumber: parsingResult.ChapterNumber, + partNumber: parsingResult.PartNumber); + } + } +} diff --git a/Emby.Naming/Book/BookFileNameParser.cs b/Emby.Naming/Book/BookFileNameParser.cs new file mode 100644 index 00000000..28625f16 --- /dev/null +++ b/Emby.Naming/Book/BookFileNameParser.cs @@ -0,0 +1,75 @@ +using System.Text.RegularExpressions; + +namespace Emby.Naming.Book +{ + /// + /// Helper class to retrieve basic metadata from a book filename. + /// + public static class BookFileNameParser + { + private const string NameMatchGroup = "name"; + private const string IndexMatchGroup = "index"; + private const string YearMatchGroup = "year"; + private const string SeriesNameMatchGroup = "seriesName"; + + private static readonly Regex[] _nameMatches = + [ + // seriesName (seriesYear) #index (of count) (year) where only seriesName and index are required + new Regex(@"^(?.+?)((\s\((?[0-9]{4})\))?)\s#(?[0-9]+)((\s\(of\s(?[0-9]+)\))?)((\s\((?[0-9]{4})\))?)$"), + new Regex(@"^(?.+?)\s\((?.+?),\s#(?[0-9]+)\)((\s\((?[0-9]{4})\))?)$"), + new Regex(@"^(?[0-9]+)\s\-\s(?.+?)((\s\((?[0-9]{4})\))?)$"), + new Regex(@"(?.*)\((?[0-9]{4})\)"), + // last resort matches the whole string as the name + new Regex(@"(?.*)") + ]; + + /// + /// Parse a filename name to retrieve the book name, series name, index, and year. + /// + /// Book filename to parse for information. + /// Returns object. + public static BookFileNameParserResult Parse(string? name) + { + var result = new BookFileNameParserResult(); + + if (name == null) + { + return result; + } + + foreach (var regex in _nameMatches) + { + var match = regex.Match(name); + + if (!match.Success) + { + continue; + } + + if (match.Groups.TryGetValue(NameMatchGroup, out Group? nameGroup) && nameGroup.Success) + { + result.Name = nameGroup.Value.Trim(); + } + + if (match.Groups.TryGetValue(IndexMatchGroup, out Group? indexGroup) && indexGroup.Success && int.TryParse(indexGroup.Value, out var index)) + { + result.Index = index; + } + + if (match.Groups.TryGetValue(YearMatchGroup, out Group? yearGroup) && yearGroup.Success && int.TryParse(yearGroup.Value, out var year)) + { + result.Year = year; + } + + if (match.Groups.TryGetValue(SeriesNameMatchGroup, out Group? seriesGroup) && seriesGroup.Success) + { + result.SeriesName = seriesGroup.Value.Trim(); + } + + break; + } + + return result; + } + } +} diff --git a/Emby.Naming/Book/BookFileNameParserResult.cs b/Emby.Naming/Book/BookFileNameParserResult.cs new file mode 100644 index 00000000..f29716b9 --- /dev/null +++ b/Emby.Naming/Book/BookFileNameParserResult.cs @@ -0,0 +1,41 @@ +using System; + +namespace Emby.Naming.Book +{ + /// + /// Data object used to pass metadata parsed from a book filename. + /// + public class BookFileNameParserResult + { + /// + /// Initializes a new instance of the class. + /// + public BookFileNameParserResult() + { + Name = null; + Index = null; + Year = null; + SeriesName = null; + } + + /// + /// Gets or sets the name of the book. + /// + public string? Name { get; set; } + + /// + /// Gets or sets the book index. + /// + public int? Index { get; set; } + + /// + /// Gets or sets the publication year. + /// + public int? Year { get; set; } + + /// + /// Gets or sets the series name. + /// + public string? SeriesName { get; set; } + } +} diff --git a/Emby.Naming/Common/EpisodeExpression.cs b/Emby.Naming/Common/EpisodeExpression.cs new file mode 100644 index 00000000..19d3c7aa --- /dev/null +++ b/Emby.Naming/Common/EpisodeExpression.cs @@ -0,0 +1,70 @@ +using System; +using System.Text.RegularExpressions; + +namespace Emby.Naming.Common +{ + /// + /// Regular expressions for parsing TV Episodes. + /// + public class EpisodeExpression + { + private string _expression; + private Regex? _regex; + + /// + /// Initializes a new instance of the class. + /// + /// Regular expressions. + /// True if date is expected. + public EpisodeExpression(string expression, bool byDate = false) + { + _expression = expression; + IsByDate = byDate; + DateTimeFormats = Array.Empty(); + SupportsAbsoluteEpisodeNumbers = true; + } + + /// + /// Gets or sets raw expressions string. + /// + public string Expression + { + get => _expression; + set + { + _expression = value; + _regex = null; + } + } + + /// + /// Gets or sets a value indicating whether gets or sets property indicating if date can be find in expression. + /// + public bool IsByDate { get; set; } + + /// + /// Gets or sets a value indicating whether gets or sets property indicating if expression is optimistic. + /// + public bool IsOptimistic { get; set; } + + /// + /// Gets or sets a value indicating whether gets or sets property indicating if expression is named. + /// + public bool IsNamed { get; set; } + + /// + /// Gets or sets a value indicating whether gets or sets property indicating if expression supports episodes with absolute numbers. + /// + public bool SupportsAbsoluteEpisodeNumbers { get; set; } + + /// + /// Gets or sets optional list of date formats used for date parsing. + /// + public string[] DateTimeFormats { get; set; } + + /// + /// Gets a expressions objects (creates it if null). + /// + public Regex Regex => _regex ??= new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled); + } +} diff --git a/Emby.Naming/Common/MediaType.cs b/Emby.Naming/Common/MediaType.cs new file mode 100644 index 00000000..dc9784c6 --- /dev/null +++ b/Emby.Naming/Common/MediaType.cs @@ -0,0 +1,23 @@ +namespace Emby.Naming.Common +{ + /// + /// Type of audiovisual media. + /// + public enum MediaType + { + /// + /// The audio. + /// + Audio = 0, + + /// + /// The photo. + /// + Photo = 1, + + /// + /// The video. + /// + Video = 2 + } +} diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs new file mode 100644 index 00000000..f61ca7e1 --- /dev/null +++ b/Emby.Naming/Common/NamingOptions.cs @@ -0,0 +1,913 @@ +#pragma warning disable CA1819 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Emby.Naming.Video; +using MediaBrowser.Model.Entities; + +// ReSharper disable StringLiteralTypo + +namespace Emby.Naming.Common +{ + /// + /// Big ugly class containing lot of different naming options that should be split and injected instead of passes everywhere. + /// + public class NamingOptions + { + /// + /// Initializes a new instance of the class. + /// + public NamingOptions() + { + VideoFileExtensions = + [ + ".001", + ".3g2", + ".3gp", + ".amv", + ".asf", + ".asx", + ".avi", + ".bin", + ".bivx", + ".divx", + ".dv", + ".dvr-ms", + ".f4v", + ".fli", + ".flv", + ".ifo", + ".img", + ".iso", + ".m2t", + ".m2ts", + ".m2v", + ".m4v", + ".mkv", + ".mk3d", + ".mov", + ".mp4", + ".mpe", + ".mpeg", + ".mpg", + ".mts", + ".mxf", + ".nrg", + ".nsv", + ".nuv", + ".ogg", + ".ogm", + ".ogv", + ".pva", + ".qt", + ".rec", + ".rm", + ".rmvb", + ".strm", + ".svq3", + ".tp", + ".ts", + ".ty", + ".viv", + ".vob", + ".vp3", + ".webm", + ".wmv", + ".wtv", + ".xvid" + ]; + + VideoFlagDelimiters = + [ + '(', + ')', + '-', + '.', + '_', + '[', + ']' + ]; + + StubFileExtensions = + [ + ".disc" + ]; + + StubTypes = + [ + new StubTypeRule( + stubType: "dvd", + token: "dvd"), + + new StubTypeRule( + stubType: "hddvd", + token: "hddvd"), + + new StubTypeRule( + stubType: "bluray", + token: "bluray"), + + new StubTypeRule( + stubType: "bluray", + token: "brrip"), + + new StubTypeRule( + stubType: "bluray", + token: "bd25"), + + new StubTypeRule( + stubType: "bluray", + token: "bd50"), + + new StubTypeRule( + stubType: "vhs", + token: "vhs"), + + new StubTypeRule( + stubType: "tv", + token: "HDTV"), + + new StubTypeRule( + stubType: "tv", + token: "PDTV"), + + new StubTypeRule( + stubType: "tv", + token: "DSR") + ]; + + VideoFileStackingRules = + [ + new FileStackRule(@"^(?.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?cd|dvd|part|pt|dis[ck])[ _.-]*(?[0-9]+)[\)\]]?(?:\.[^.]+)?$", true), + new FileStackRule(@"^(?.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?cd|dvd|part|pt|dis[ck])[ _.-]*(?[a-d])[\)\]]?(?:\.[^.]+)?$", false) + ]; + + CleanDateTimes = + [ + @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*", + @"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*" + ]; + + CleanStrings = + [ + @"^\s*(?.+?)[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multi|subs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r5|bd5|bd|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|blu-ray|x264|x265|h264|h265|xvid|xvidvd|xxx|www.www|AAC|DTS|\[.*\])([ _\,\.\(\)\[\]\-]|$)", + @"^(?.+?)(\[.*\])", + @"^\s*(?.+?)\WE[0-9]+(-|~)E?[0-9]+(\W|$)", + @"^\s*\[[^\]]+\](?!\.\w+$)\s*(?.+)", + @"^\s*(?.+?)\s+-\s+[0-9]+\s*$", + @"^\s*(?.+?)(([-._ ](trailer|sample))|-(scene|clip|behindthescenes|deleted|deletedscene|featurette|short|interview|other|extra))$" + ]; + + SubtitleFileExtensions = + [ + ".ass", + ".mks", + ".sami", + ".smi", + ".srt", + ".ssa", + ".sub", + ".sup", + ".vtt", + ]; + + LyricFileExtensions = + [ + ".lrc", + ".elrc", + ".txt" + ]; + + AlbumStackingPrefixes = + [ + "cd", + "digital media", + "disc", + "disk", + "vol", + "volume", + "part", + "act" + ]; + + ArtistSubfolders = + [ + "albums", + "broadcasts", + "bootlegs", + "compilations", + "dj-mixes", + "eps", + "live", + "mixtapes", + "others", + "remixes", + "singles", + "soundtracks", + "spokenwords", + "streets" + ]; + + AudioFileExtensions = + [ + ".669", + ".3gp", + ".aa", + ".aac", + ".aax", + ".ac3", + ".act", + ".adp", + ".adplug", + ".adx", + ".afc", + ".amf", + ".aif", + ".aiff", + ".alac", + ".amr", + ".ape", + ".ast", + ".au", + ".awb", + ".cda", + ".cue", + ".dmf", + ".dsf", + ".dsm", + ".dsp", + ".dts", + ".dvf", + ".eac3", + ".ec3", + ".far", + ".flac", + ".gdm", + ".gsm", + ".gym", + ".hps", + ".imf", + ".it", + ".m15", + ".m4a", + ".m4b", + ".mac", + ".med", + ".mka", + ".mmf", + ".mod", + ".mogg", + ".mp2", + ".mp3", + ".mpa", + ".mpc", + ".mpp", + ".mp+", + ".msv", + ".nmf", + ".nsf", + ".nsv", + ".oga", + ".ogg", + ".okt", + ".opus", + ".pls", + ".ra", + ".rf64", + ".rm", + ".s3m", + ".sfx", + ".shn", + ".sid", + ".stm", + ".strm", + ".ult", + ".uni", + ".vox", + ".wav", + ".wma", + ".wv", + ".xm", + ".xsp", + ".ymf" + ]; + + MediaFlagDelimiters = + [ + '.' + ]; + + MediaForcedFlags = + [ + "foreign", + "forced" + ]; + + MediaDefaultFlags = + [ + "default" + ]; + + MediaHearingImpairedFlags = + [ + "cc", + "hi", + "sdh" + ]; + + EpisodeExpressions = + [ + // *** Begin Kodi Standard Naming + // + new EpisodeExpression(@".*(\\|\/)(?((?![Ss]([0-9]+)[][ ._-]*[Ee]([0-9]+))[^\\\/])*)?[Ss](?[0-9]+)[][ ._-]*[Ee](?[0-9]+)([^\\/]*)$") + { + IsNamed = true + }, + // + new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"), + // + new EpisodeExpression(@"[^\\/]*?()\.?[Ee]([0-9]+)\.([^\\/]*)$"), + new EpisodeExpression("(?[0-9]{4})[._ -](?[0-9]{2})[._ -](?[0-9]{2})", true) + { + DateTimeFormats = + [ + "yyyy.MM.dd", + "yyyy-MM-dd", + "yyyy_MM_dd", + "yyyy MM dd" + ] + }, + new EpisodeExpression("(?[0-9]{2})[._ -](?[0-9]{2})[._ -](?[0-9]{4})", true) + { + DateTimeFormats = + [ + "dd.MM.yyyy", + "dd-MM-yyyy", + "dd_MM_yyyy", + "dd MM yyyy" + ] + }, + + // This isn't a Kodi naming rule, but the expression below causes false episode numbers for + // Title Season X Episode X naming schemes. + // "Series Season X Episode X - Title.avi", "Series S03 E09.avi", "s3 e9 - Title.avi" + new EpisodeExpression(@".*[\\\/]((?[^\\/]+?)\s)?[Ss](?:eason)?\s*(?[0-9]+)\s+[Ee](?:pisode)?\s*(?[0-9]+).*$") + { + IsNamed = true + }, + + // Not a Kodi rule as well, but the expression below also causes false positives, + // so we make sure this one gets tested first. + // "Foo Bar 889" + new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?[\w\s]+?)\s(?[0-9]{1,4})(-(?[0-9]{2,4}))*[^\\\/x]*$") + { + IsNamed = true + }, + + new EpisodeExpression(@"[\\\/\._ \[\(-]([0-9]+)x([0-9]+(?:(?:[a-i]|\.[1-9])(?![0-9]))?)([^\\\/]*)$") + { + SupportsAbsoluteEpisodeNumbers = true + }, + + // Not a Kodi rule as well, but below rule also causes false positives for triple-digit episode names + // [bar] Foo - 1 [baz] special case of below expression to prevent false positives with digits in the series name + new EpisodeExpression(@".*[\\\/]?.*?(\[.*?\])+.*?(?[-\w\s]+?)[\s_]*-[\s_]*(?[0-9]+).*$") + { + IsNamed = true + }, + + // /server/anything_102.mp4 + // /server/james.corden.2017.04.20.anne.hathaway.720p.hdtv.x264-crooks.mkv + // /server/anything_1996.11.14.mp4 + new EpisodeExpression(@"[\\/._ -](?(?![0-9]+[0-9][0-9])([^\\\/_])*)[\\\/._ -](?[0-9]+)(?[0-9][0-9](?:(?:[a-i]|\.[1-9])(?![0-9]))?)([._ -][^\\\/]*)$") + { + IsOptimistic = true, + IsNamed = true, + SupportsAbsoluteEpisodeNumbers = false + }, + new EpisodeExpression(@"[\/._ -]p(?:ar)?t[_. -]()([ivx]+|[0-9]+)([._ -][^\/]*)$") + { + SupportsAbsoluteEpisodeNumbers = true + }, + + // *** End Kodi Standard Naming + + // "Episode 16", "Episode 16 - Title" + new EpisodeExpression(@"[Ee]pisode (?[0-9]+)(-(?[0-9]+))?[^\\\/]*$") + { + IsNamed = true + }, + + new EpisodeExpression(@".*(\\|\/)[sS]?(?[0-9]+)[xX](?[0-9]+)[^\\\/]*$") + { + IsNamed = true + }, + + new EpisodeExpression(@".*(\\|\/)[sS](?[0-9]+)[x,X]?[eE](?[0-9]+)[^\\\/]*$") + { + IsNamed = true + }, + + new EpisodeExpression(@".*(\\|\/)(?((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?[0-9]{1,4})[xX](?[0-9]+))[^\\\/]*$") + { + IsNamed = true + }, + + new EpisodeExpression(@".*(\\|\/)(?[^\\\/]*)[sS](?[0-9]{1,4})[xX\.]?[eE](?[0-9]+)[^\\\/]*$") + { + IsNamed = true + }, + + // "01.avi" + new EpisodeExpression(@".*[\\\/](?[0-9]+)(-(?[0-9]+))*\.\w+$") + { + IsOptimistic = true, + IsNamed = true + }, + + // "1-12 episode title" + new EpisodeExpression("([0-9]+)-([0-9]+)"), + + // "01 - blah.avi", "01-blah.avi" + new EpisodeExpression(@".*(\\|\/)(?[0-9]{1,3})(-(?[0-9]{2,3}))*\s?-\s?[^\\\/]*$") + { + IsOptimistic = true, + IsNamed = true + }, + + // "01.blah.avi" + new EpisodeExpression(@".*(\\|\/)(?[0-9]{1,3})(-(?[0-9]{2,3}))*\.[^\\\/]+$") + { + IsOptimistic = true, + IsNamed = true + }, + + // "blah - 01.avi", "blah 2 - 01.avi", "blah - 01 blah.avi", "blah 2 - 01 blah", "blah - 01 - blah.avi", "blah 2 - 01 - blah" + new EpisodeExpression(@".*[\\\/][^\\\/]* - (?[0-9]{1,3})(-(?[0-9]{2,3}))*[^\\\/]*$") + { + IsOptimistic = true, + IsNamed = true + }, + + // "01 episode title.avi" + new EpisodeExpression(@"[Ss]eason[\._ ](?[0-9]+)[\\\/](?[0-9]{1,3})([^\\\/]*)$") + { + IsOptimistic = true, + IsNamed = true + }, + + // Series and season only expression + // "the show/season 1", "the show/s01" + new EpisodeExpression(@"(.*(\\|\/))*(?.+)\/[Ss](eason)?[\. _\-]*(?[0-9]+)") + { + IsNamed = true + }, + + // Series and season only expression + // "the show S01", "the show season 1" + new EpisodeExpression(@"(.*(\\|\/))*(?.+)[\. _\-]+[sS](eason)?[\. _\-]*(?[0-9]+)") + { + IsNamed = true + }, + + // Anime style expression + // "[Group][Series Name][21][1080p][FLAC][HASH]" + // "[Group] Series Name [04][BDRIP]" + new EpisodeExpression(@"(?:\[(?:[^\]]+)\]\s*)?(?\[[^\]]+\]|[^[\]]+)\s*\[(?[0-9]+)\]") + { + IsNamed = true + }, + ]; + + VideoExtraRules = + [ + new ExtraRule( + ExtraType.Trailer, + ExtraRuleType.DirectoryName, + "trailers", + MediaType.Video), + + new ExtraRule( + ExtraType.ThemeVideo, + ExtraRuleType.DirectoryName, + "backdrops", + MediaType.Video), + + new ExtraRule( + ExtraType.ThemeSong, + ExtraRuleType.DirectoryName, + "theme-music", + MediaType.Audio), + + new ExtraRule( + ExtraType.BehindTheScenes, + ExtraRuleType.DirectoryName, + "behind the scenes", + MediaType.Video), + + new ExtraRule( + ExtraType.DeletedScene, + ExtraRuleType.DirectoryName, + "deleted scenes", + MediaType.Video), + + new ExtraRule( + ExtraType.Interview, + ExtraRuleType.DirectoryName, + "interviews", + MediaType.Video), + + new ExtraRule( + ExtraType.Scene, + ExtraRuleType.DirectoryName, + "scenes", + MediaType.Video), + + new ExtraRule( + ExtraType.Sample, + ExtraRuleType.DirectoryName, + "samples", + MediaType.Video), + + new ExtraRule( + ExtraType.Short, + ExtraRuleType.DirectoryName, + "shorts", + MediaType.Video), + + new ExtraRule( + ExtraType.Featurette, + ExtraRuleType.DirectoryName, + "featurettes", + MediaType.Video), + + new ExtraRule( + ExtraType.Unknown, + ExtraRuleType.DirectoryName, + "extras", + MediaType.Video), + + new ExtraRule( + ExtraType.Unknown, + ExtraRuleType.DirectoryName, + "extra", + MediaType.Video), + + new ExtraRule( + ExtraType.Unknown, + ExtraRuleType.DirectoryName, + "other", + MediaType.Video), + + new ExtraRule( + ExtraType.Clip, + ExtraRuleType.DirectoryName, + "clips", + MediaType.Video), + + new ExtraRule( + ExtraType.Trailer, + ExtraRuleType.Filename, + "trailer", + MediaType.Video), + + new ExtraRule( + ExtraType.Sample, + ExtraRuleType.Filename, + "sample", + MediaType.Video), + + new ExtraRule( + ExtraType.ThemeSong, + ExtraRuleType.Filename, + "theme", + MediaType.Audio), + + new ExtraRule( + ExtraType.Trailer, + ExtraRuleType.Suffix, + "-trailer", + MediaType.Video), + + new ExtraRule( + ExtraType.Trailer, + ExtraRuleType.Suffix, + ".trailer", + MediaType.Video), + + new ExtraRule( + ExtraType.Trailer, + ExtraRuleType.Suffix, + "_trailer", + MediaType.Video), + + new ExtraRule( + ExtraType.Trailer, + ExtraRuleType.Suffix, + "- trailer", + MediaType.Video), + + new ExtraRule( + ExtraType.Sample, + ExtraRuleType.Suffix, + "-sample", + MediaType.Video), + + new ExtraRule( + ExtraType.Sample, + ExtraRuleType.Suffix, + ".sample", + MediaType.Video), + + new ExtraRule( + ExtraType.Sample, + ExtraRuleType.Suffix, + "_sample", + MediaType.Video), + + new ExtraRule( + ExtraType.Sample, + ExtraRuleType.Suffix, + "- sample", + MediaType.Video), + + new ExtraRule( + ExtraType.Scene, + ExtraRuleType.Suffix, + "-scene", + MediaType.Video), + + new ExtraRule( + ExtraType.Clip, + ExtraRuleType.Suffix, + "-clip", + MediaType.Video), + + new ExtraRule( + ExtraType.Interview, + ExtraRuleType.Suffix, + "-interview", + MediaType.Video), + + new ExtraRule( + ExtraType.BehindTheScenes, + ExtraRuleType.Suffix, + "-behindthescenes", + MediaType.Video), + + new ExtraRule( + ExtraType.DeletedScene, + ExtraRuleType.Suffix, + "-deleted", + MediaType.Video), + + new ExtraRule( + ExtraType.DeletedScene, + ExtraRuleType.Suffix, + "-deletedscene", + MediaType.Video), + + new ExtraRule( + ExtraType.Featurette, + ExtraRuleType.Suffix, + "-featurette", + MediaType.Video), + + new ExtraRule( + ExtraType.Short, + ExtraRuleType.Suffix, + "-short", + MediaType.Video), + + new ExtraRule( + ExtraType.Unknown, + ExtraRuleType.Suffix, + "-extra", + MediaType.Video), + + new ExtraRule( + ExtraType.Unknown, + ExtraRuleType.Suffix, + "-other", + MediaType.Video) + ]; + + AllExtrasTypesFolderNames = VideoExtraRules + .Where(i => i.RuleType == ExtraRuleType.DirectoryName) + .ToDictionary(i => i.Token, i => i.ExtraType, StringComparer.OrdinalIgnoreCase); + + Format3DRules = + [ + // Kodi rules: + new Format3DRule( + precedingToken: "3d", + token: "hsbs"), + + new Format3DRule( + precedingToken: "3d", + token: "sbs"), + + new Format3DRule( + precedingToken: "3d", + token: "htab"), + + new Format3DRule( + precedingToken: "3d", + token: "tab"), + + // Media Browser rules: + new Format3DRule("fsbs"), + new Format3DRule("hsbs"), + new Format3DRule("sbs"), + new Format3DRule("ftab"), + new Format3DRule("htab"), + new Format3DRule("tab"), + new Format3DRule("sbs3d"), + new Format3DRule("mvc") + ]; + + AudioBookPartsExpressions = + [ + // Detect specified chapters, like CH 01 + @"ch(?:apter)?[\s_-]?(?[0-9]+)", + // Detect specified parts, like Part 02 + @"p(?:ar)?t[\s_-]?(?[0-9]+)", + // Chapter is often beginning of filename + "^(?[0-9]+)", + // Part if often ending of filename + "(?[0-9]+)$", + // Sometimes named as 0001_005 (chapter_part) + "(?[0-9]+)_(?[0-9]+)", + // Some audiobooks are ripped from cd's, and will be named by disk number. + @"dis(?:c|k)[\s_-]?(?[0-9]+)" + ]; + + AudioBookNamesExpressions = + [ + // Detect year usually in brackets after name Batman (2020) + @"^(?.+?)\s*\(\s*(?[0-9]{4})\s*\)\s*$", + @"^\s*(?[^ ].*?)\s*$" + ]; + + MultipleEpisodeExpressions = new[] + { + @".*(\\|\/)[sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3})((-| - )[0-9]{1,4}[eExX](?[0-9]{1,3}))+[^\\\/]*$", + @".*(\\|\/)[sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3})((-| - )[0-9]{1,4}[xX][eE](?[0-9]{1,3}))+[^\\\/]*$", + @".*(\\|\/)[sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3})((-| - )?[xXeE](?[0-9]{1,3}))+[^\\\/]*$", + @".*(\\|\/)[sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3})(-[xE]?[eE]?(?[0-9]{1,3}))+[^\\\/]*$", + @".*(\\|\/)(?((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3}))((-| - )[0-9]{1,4}[xXeE](?[0-9]{1,3}))+[^\\\/]*$", + @".*(\\|\/)(?((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3}))((-| - )[0-9]{1,4}[xX][eE](?[0-9]{1,3}))+[^\\\/]*$", + @".*(\\|\/)(?((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3}))((-| - )?[xXeE](?[0-9]{1,3}))+[^\\\/]*$", + @".*(\\|\/)(?((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3}))(-[xX]?[eE]?(?[0-9]{1,3}))+[^\\\/]*$", + @".*(\\|\/)(?[^\\\/]*)[sS](?[0-9]{1,4})[xX\.]?[eE](?[0-9]{1,3})((-| - )?[xXeE](?[0-9]{1,3}))+[^\\\/]*$", + @".*(\\|\/)(?[^\\\/]*)[sS](?[0-9]{1,4})[xX\.]?[eE](?[0-9]{1,3})(-[xX]?[eE]?(?[0-9]{1,3}))+[^\\\/]*$" + }.Select(i => new EpisodeExpression(i) + { + IsNamed = true + }).ToArray(); + + Compile(); + } + + /// + /// Gets or sets the folder name to extra types mapping. + /// + public Dictionary AllExtrasTypesFolderNames { get; set; } + + /// + /// Gets or sets list of audio file extensions. + /// + public string[] AudioFileExtensions { get; set; } + + /// + /// Gets or sets list of external media flag delimiters. + /// + public char[] MediaFlagDelimiters { get; set; } + + /// + /// Gets or sets list of external media forced flags. + /// + public string[] MediaForcedFlags { get; set; } + + /// + /// Gets or sets list of external media default flags. + /// + public string[] MediaDefaultFlags { get; set; } + + /// + /// Gets or sets list of external media hearing impaired flags. + /// + public string[] MediaHearingImpairedFlags { get; set; } + + /// + /// Gets or sets list of album stacking prefixes. + /// + public string[] AlbumStackingPrefixes { get; set; } + + /// + /// Gets or sets list of artist subfolders. + /// + public string[] ArtistSubfolders { get; set; } + + /// + /// Gets or sets list of subtitle file extensions. + /// + public string[] SubtitleFileExtensions { get; set; } + + /// + /// Gets the list of lyric file extensions. + /// + public string[] LyricFileExtensions { get; } + + /// + /// Gets or sets list of episode regular expressions. + /// + public EpisodeExpression[] EpisodeExpressions { get; set; } + + /// + /// Gets or sets list of video file extensions. + /// + public string[] VideoFileExtensions { get; set; } + + /// + /// Gets or sets list of video stub file extensions. + /// + public string[] StubFileExtensions { get; set; } + + /// + /// Gets or sets list of raw audiobook parts regular expressions strings. + /// + public string[] AudioBookPartsExpressions { get; set; } + + /// + /// Gets or sets list of raw audiobook names regular expressions strings. + /// + public string[] AudioBookNamesExpressions { get; set; } + + /// + /// Gets or sets list of stub type rules. + /// + public StubTypeRule[] StubTypes { get; set; } + + /// + /// Gets or sets list of video flag delimiters. + /// + public char[] VideoFlagDelimiters { get; set; } + + /// + /// Gets or sets list of 3D Format rules. + /// + public Format3DRule[] Format3DRules { get; set; } + + /// + /// Gets the file stacking rules. + /// + public FileStackRule[] VideoFileStackingRules { get; } + + /// + /// Gets or sets list of raw clean DateTimes regular expressions strings. + /// + public string[] CleanDateTimes { get; set; } + + /// + /// Gets or sets list of raw clean strings regular expressions strings. + /// + public string[] CleanStrings { get; set; } + + /// + /// Gets or sets list of multi-episode regular expressions. + /// + public EpisodeExpression[] MultipleEpisodeExpressions { get; set; } + + /// + /// Gets or sets list of extra rules for videos. + /// + public ExtraRule[] VideoExtraRules { get; set; } + + /// + /// Gets list of clean datetime regular expressions. + /// + public Regex[] CleanDateTimeRegexes { get; private set; } = []; + + /// + /// Gets list of clean string regular expressions. + /// + public Regex[] CleanStringRegexes { get; private set; } = []; + + /// + /// Compiles raw regex strings into regexes. + /// + public void Compile() + { + CleanDateTimeRegexes = CleanDateTimes.Select(Compile).ToArray(); + CleanStringRegexes = CleanStrings.Select(Compile).ToArray(); + } + + private Regex Compile(string exp) + { + return new Regex(exp, RegexOptions.IgnoreCase | RegexOptions.Compiled); + } + } +} diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj new file mode 100644 index 00000000..97b52e42 --- /dev/null +++ b/Emby.Naming/Emby.Naming.csproj @@ -0,0 +1,59 @@ + + + + + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2} + + + + net10.0 + false + true + true + true + true + snupkg + + + + false + + + + + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + + + + + + + + + + + + Jellyfin Contributors + Jellyfin.Naming + 10.12.0 + https://github.com/jellyfin/jellyfin + GPL-3.0-only + + + + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + diff --git a/Emby.Naming/ExternalFiles/ExternalPathParser.cs b/Emby.Naming/ExternalFiles/ExternalPathParser.cs new file mode 100644 index 00000000..3461b3c0 --- /dev/null +++ b/Emby.Naming/ExternalFiles/ExternalPathParser.cs @@ -0,0 +1,133 @@ +using System; +using System.IO; +using System.Linq; +using Emby.Naming.Common; +using Jellyfin.Extensions; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Globalization; + +namespace Emby.Naming.ExternalFiles +{ + /// + /// External media file parser class. + /// + public class ExternalPathParser + { + private readonly NamingOptions _namingOptions; + private readonly DlnaProfileType _type; + private readonly ILocalizationManager _localizationManager; + + /// + /// Initializes a new instance of the class. + /// + /// The localization manager. + /// The object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters. + /// The of the parsed file. + public ExternalPathParser(NamingOptions namingOptions, ILocalizationManager localizationManager, DlnaProfileType type) + { + _localizationManager = localizationManager; + _namingOptions = namingOptions; + _type = type; + } + + /// + /// Parse filename and extract information. + /// + /// Path to file. + /// Part of the filename only containing the extra information. + /// Returns null or an object if parsing is successful. + public ExternalPathParserResult? ParseFile(string path, string? extraString) + { + if (path.Length == 0) + { + return null; + } + + var extension = Path.GetExtension(path.AsSpan()); + if (!(_type == DlnaProfileType.Subtitle && _namingOptions.SubtitleFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) + && !(_type == DlnaProfileType.Audio && _namingOptions.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) + && !(_type == DlnaProfileType.Lyric && _namingOptions.LyricFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))) + { + return null; + } + + var pathInfo = new ExternalPathParserResult(path); + + if (string.IsNullOrEmpty(extraString)) + { + return pathInfo; + } + + foreach (var separator in _namingOptions.MediaFlagDelimiters) + { + var languageString = extraString; + var titleString = string.Empty; + const int SeparatorLength = 1; + + while (languageString.Length > 0) + { + int lastSeparator = languageString.LastIndexOf(separator); + + if (lastSeparator == -1) + { + break; + } + + string currentSlice = languageString[lastSeparator..]; + string currentSliceWithoutSeparator = currentSlice[SeparatorLength..]; + + if (_namingOptions.MediaDefaultFlags.Any(s => currentSliceWithoutSeparator.Contains(s, StringComparison.OrdinalIgnoreCase))) + { + pathInfo.IsDefault = true; + extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase); + languageString = languageString[..lastSeparator]; + continue; + } + + if (_namingOptions.MediaForcedFlags.Any(s => currentSliceWithoutSeparator.Contains(s, StringComparison.OrdinalIgnoreCase))) + { + pathInfo.IsForced = true; + extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase); + languageString = languageString[..lastSeparator]; + continue; + } + + // Try to translate to three character code + var culture = _localizationManager.FindLanguageInfo(currentSliceWithoutSeparator); + + if (culture is not null && pathInfo.Language is null) + { + pathInfo.Language = culture.Name.Contains('-', StringComparison.OrdinalIgnoreCase) + ? culture.Name + : culture.ThreeLetterISOLanguageName; + extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase); + } + else if (culture is not null && pathInfo.Language == "hin") + { + // Hindi language code "hi" collides with a hearing impaired flag - use as Hindi only if no other language is set + pathInfo.IsHearingImpaired = true; + pathInfo.Language = culture.Name.Contains('-', StringComparison.OrdinalIgnoreCase) + ? culture.Name + : culture.ThreeLetterISOLanguageName; + extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase); + } + else if (_namingOptions.MediaHearingImpairedFlags.Any(s => currentSliceWithoutSeparator.Equals(s, StringComparison.OrdinalIgnoreCase))) + { + pathInfo.IsHearingImpaired = true; + extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase); + } + else + { + titleString = currentSlice + titleString; + } + + languageString = languageString[..lastSeparator]; + } + + pathInfo.Title = titleString.Length >= SeparatorLength ? titleString[SeparatorLength..] : null; + } + + return pathInfo; + } + } +} diff --git a/Emby.Naming/ExternalFiles/ExternalPathParserResult.cs b/Emby.Naming/ExternalFiles/ExternalPathParserResult.cs new file mode 100644 index 00000000..b0d9e7a9 --- /dev/null +++ b/Emby.Naming/ExternalFiles/ExternalPathParserResult.cs @@ -0,0 +1,59 @@ +namespace Emby.Naming.ExternalFiles +{ + /// + /// Class holding information about external files. + /// + public class ExternalPathParserResult + { + /// + /// Initializes a new instance of the class. + /// + /// Path to file. + /// Is default. + /// Is forced. + /// For the hearing impaired. + public ExternalPathParserResult(string path, bool isDefault = false, bool isForced = false, bool isHearingImpaired = false) + { + Path = path; + IsDefault = isDefault; + IsForced = isForced; + IsHearingImpaired = isHearingImpaired; + } + + /// + /// Gets or sets the path. + /// + /// The path. + public string Path { get; set; } + + /// + /// Gets or sets the language. + /// + /// The language. + public string? Language { get; set; } + + /// + /// Gets or sets the title. + /// + /// The title. + public string? Title { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is default. + /// + /// true if this instance is default; otherwise, false. + public bool IsDefault { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is forced. + /// + /// true if this instance is forced; otherwise, false. + public bool IsForced { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is for the hearing impaired. + /// + /// true if this instance is for the hearing impaired; otherwise, false. + public bool IsHearingImpaired { get; set; } + } +} diff --git a/Emby.Naming/Properties/AssemblyInfo.cs b/Emby.Naming/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..f26e0ba7 --- /dev/null +++ b/Emby.Naming/Properties/AssemblyInfo.cs @@ -0,0 +1,21 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Emby.Naming")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Jellyfin Project")] +[assembly: AssemblyProduct("Jellyfin Server")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] diff --git a/Emby.Naming/TV/EpisodeInfo.cs b/Emby.Naming/TV/EpisodeInfo.cs new file mode 100644 index 00000000..a8920b36 --- /dev/null +++ b/Emby.Naming/TV/EpisodeInfo.cs @@ -0,0 +1,94 @@ +namespace Emby.Naming.TV +{ + /// + /// Holder object for Episode information. + /// + public class EpisodeInfo + { + /// + /// Initializes a new instance of the class. + /// + /// Path to the file. + public EpisodeInfo(string path) + { + Path = path; + } + + /// + /// Gets or sets the path. + /// + /// The path. + public string Path { get; set; } + + /// + /// Gets or sets the container. + /// + /// The container. + public string? Container { get; set; } + + /// + /// Gets or sets the name of the series. + /// + /// The name of the series. + public string? SeriesName { get; set; } + + /// + /// Gets or sets the format3 d. + /// + /// The format3 d. + public string? Format3D { get; set; } + + /// + /// Gets or sets a value indicating whether [is3 d]. + /// + /// true if [is3 d]; otherwise, false. + public bool Is3D { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is stub. + /// + /// true if this instance is stub; otherwise, false. + public bool IsStub { get; set; } + + /// + /// Gets or sets the type of the stub. + /// + /// The type of the stub. + public string? StubType { get; set; } + + /// + /// Gets or sets optional season number. + /// + public int? SeasonNumber { get; set; } + + /// + /// Gets or sets optional episode number. + /// + public int? EpisodeNumber { get; set; } + + /// + /// Gets or sets optional ending episode number. For multi-episode files 1-13. + /// + public int? EndingEpisodeNumber { get; set; } + + /// + /// Gets or sets optional year of release. + /// + public int? Year { get; set; } + + /// + /// Gets or sets optional year of release. + /// + public int? Month { get; set; } + + /// + /// Gets or sets optional day of release. + /// + public int? Day { get; set; } + + /// + /// Gets or sets a value indicating whether by date expression was used. + /// + public bool IsByDate { get; set; } + } +} diff --git a/Emby.Naming/TV/EpisodePathParser.cs b/Emby.Naming/TV/EpisodePathParser.cs new file mode 100644 index 00000000..8cd5a126 --- /dev/null +++ b/Emby.Naming/TV/EpisodePathParser.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Emby.Naming.Common; + +namespace Emby.Naming.TV +{ + /// + /// Used to parse information about episode from path. + /// + public class EpisodePathParser + { + private readonly NamingOptions _options; + + /// + /// Initializes a new instance of the class. + /// + /// object containing EpisodeExpressions and MultipleEpisodeExpressions. + public EpisodePathParser(NamingOptions options) + { + _options = options; + } + + /// + /// Parses information about episode from path. + /// + /// Path. + /// Is path for a directory or file. + /// Do we want to use IsNamed expressions. + /// Do we want to use Optimistic expressions. + /// Do we want to use expressions supporting absolute episode numbers. + /// Should we attempt to retrieve extended information. + /// Returns object. + public EpisodePathParserResult Parse( + string path, + bool isDirectory, + bool? isNamed = null, + bool? isOptimistic = null, + bool? supportsAbsoluteNumbers = null, + bool fillExtendedInfo = true) + { + // Added to be able to use regex patterns which require a file extension. + // There were no failed tests without this block, but to be safe, we can keep it until + // the regex which require file extensions are modified so that they don't need them. + if (isDirectory) + { + path += ".mp4"; + } + + EpisodePathParserResult? result = null; + + foreach (var expression in _options.EpisodeExpressions) + { + if (supportsAbsoluteNumbers.HasValue + && expression.SupportsAbsoluteEpisodeNumbers != supportsAbsoluteNumbers.Value) + { + continue; + } + + if (isNamed.HasValue && expression.IsNamed != isNamed.Value) + { + continue; + } + + if (isOptimistic.HasValue && expression.IsOptimistic != isOptimistic.Value) + { + continue; + } + + var currentResult = Parse(path, expression); + if (currentResult.Success) + { + result = currentResult; + break; + } + } + + if (result is not null && fillExtendedInfo) + { + FillAdditional(path, result); + + if (!string.IsNullOrEmpty(result.SeriesName)) + { + result.SeriesName = result.SeriesName + .Trim() + .Trim('_', '.', '-') + .Trim(); + } + } + + return result ?? new EpisodePathParserResult(); + } + + private static EpisodePathParserResult Parse(string name, EpisodeExpression expression) + { + var result = new EpisodePathParserResult(); + + // This is a hack to handle wmc naming + if (expression.IsByDate) + { + name = name.Replace('_', '-'); + } + + var match = expression.Regex.Match(name); + + // (Full)(Season)(Episode)(Extension) + if (match.Success && match.Groups.Count >= 3) + { + if (expression.IsByDate) + { + DateTime date; + if (expression.DateTimeFormats.Length > 0) + { + if (DateTime.TryParseExact( + match.Groups[0].ValueSpan, + expression.DateTimeFormats, + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out date)) + { + result.Year = date.Year; + result.Month = date.Month; + result.Day = date.Day; + result.Success = true; + } + } + else if (DateTime.TryParse(match.Groups[0].ValueSpan, out date)) + { + result.Year = date.Year; + result.Month = date.Month; + result.Day = date.Day; + result.Success = true; + } + + // TODO: Only consider success if date successfully parsed? + result.Success = true; + } + else if (expression.IsNamed) + { + if (int.TryParse(match.Groups["seasonnumber"].ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var num)) + { + result.SeasonNumber = num; + } + + if (int.TryParse(match.Groups["epnumber"].ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out num)) + { + result.EpisodeNumber = num; + } + + var endingNumberGroup = match.Groups["endingepnumber"]; + if (endingNumberGroup.Success) + { + // Will only set EndingEpisodeNumber if the captured number is not followed by additional numbers + // or a 'p' or 'i' as what you would get with a pixel resolution specification. + // It avoids erroneous parsing of something like "series-s09e14-1080p.mkv" as a multi-episode from E14 to E108 + int nextIndex = endingNumberGroup.Index + endingNumberGroup.Length; + if (nextIndex >= name.Length + || !"0123456789iIpP".Contains(name[nextIndex], StringComparison.Ordinal)) + { + if (int.TryParse(endingNumberGroup.ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out num)) + { + result.EndingEpisodeNumber = num; + } + } + } + + result.SeriesName = match.Groups["seriesname"].Value; + result.Success = result.EpisodeNumber.HasValue; + } + else + { + if (int.TryParse(match.Groups[1].ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var num)) + { + result.SeasonNumber = num; + } + + if (int.TryParse(match.Groups[2].ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out num)) + { + result.EpisodeNumber = num; + } + + result.Success = result.EpisodeNumber.HasValue; + } + + // Invalidate match when the season is 200 through 1927 or above 2500 + // because it is an error unless the TV show is intentionally using false season numbers. + // It avoids erroneous parsing of something like "Series Special (1920x1080).mkv" as being season 1920 episode 1080. + if ((result.SeasonNumber >= 200 && result.SeasonNumber < 1928) + || result.SeasonNumber > 2500) + { + result.Success = false; + } + + result.IsByDate = expression.IsByDate; + } + + return result; + } + + private void FillAdditional(string path, EpisodePathParserResult info) + { + var expressions = _options.MultipleEpisodeExpressions.Where(i => i.IsNamed).ToList(); + + if (string.IsNullOrEmpty(info.SeriesName)) + { + expressions.InsertRange(0, _options.EpisodeExpressions.Where(i => i.IsNamed)); + } + + FillAdditional(path, info, expressions); + } + + private void FillAdditional(string path, EpisodePathParserResult info, IEnumerable expressions) + { + foreach (var i in expressions) + { + var result = Parse(path, i); + + if (!result.Success) + { + continue; + } + + if (string.IsNullOrEmpty(info.SeriesName)) + { + info.SeriesName = result.SeriesName; + } + + if (!info.EndingEpisodeNumber.HasValue && info.EpisodeNumber.HasValue) + { + info.EndingEpisodeNumber = result.EndingEpisodeNumber; + } + + if (!string.IsNullOrEmpty(info.SeriesName) + && (!info.EpisodeNumber.HasValue || info.EndingEpisodeNumber.HasValue)) + { + break; + } + } + } + } +} diff --git a/Emby.Naming/TV/EpisodePathParserResult.cs b/Emby.Naming/TV/EpisodePathParserResult.cs new file mode 100644 index 00000000..233d5a4f --- /dev/null +++ b/Emby.Naming/TV/EpisodePathParserResult.cs @@ -0,0 +1,54 @@ +namespace Emby.Naming.TV +{ + /// + /// Holder object for result. + /// + public class EpisodePathParserResult + { + /// + /// Gets or sets optional season number. + /// + public int? SeasonNumber { get; set; } + + /// + /// Gets or sets optional episode number. + /// + public int? EpisodeNumber { get; set; } + + /// + /// Gets or sets optional ending episode number. For multi-episode files 1-13. + /// + public int? EndingEpisodeNumber { get; set; } + + /// + /// Gets or sets the name of the series. + /// + /// The name of the series. + public string? SeriesName { get; set; } + + /// + /// Gets or sets a value indicating whether parsing was successful. + /// + public bool Success { get; set; } + + /// + /// Gets or sets a value indicating whether by date expression was used. + /// + public bool IsByDate { get; set; } + + /// + /// Gets or sets optional year of release. + /// + public int? Year { get; set; } + + /// + /// Gets or sets optional year of release. + /// + public int? Month { get; set; } + + /// + /// Gets or sets optional day of release. + /// + public int? Day { get; set; } + } +} diff --git a/Emby.Naming/TV/EpisodeResolver.cs b/Emby.Naming/TV/EpisodeResolver.cs new file mode 100644 index 00000000..6cebc40c --- /dev/null +++ b/Emby.Naming/TV/EpisodeResolver.cs @@ -0,0 +1,93 @@ +using System; +using System.IO; +using Emby.Naming.Common; +using Emby.Naming.Video; +using Jellyfin.Extensions; + +namespace Emby.Naming.TV +{ + /// + /// Used to resolve information about episode from path. + /// + public class EpisodeResolver + { + private readonly NamingOptions _options; + + /// + /// Initializes a new instance of the class. + /// + /// object containing VideoFileExtensions and passed to , and . + public EpisodeResolver(NamingOptions options) + { + _options = options; + } + + /// + /// Resolve information about episode from path. + /// + /// Path. + /// Is path for a directory or file. + /// Do we want to use IsNamed expressions. + /// Do we want to use Optimistic expressions. + /// Do we want to use expressions supporting absolute episode numbers. + /// Should we attempt to retrieve extended information. + /// Returns null or object if successful. + public EpisodeInfo? Resolve( + string path, + bool isDirectory, + bool? isNamed = null, + bool? isOptimistic = null, + bool? supportsAbsoluteNumbers = null, + bool fillExtendedInfo = true) + { + bool isStub = false; + string? container = null; + string? stubType = null; + + if (!isDirectory) + { + var extension = Path.GetExtension(path); + // Check supported extensions + if (!_options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) + { + // It's not supported. Check stub extensions + if (!StubResolver.TryResolveFile(path, _options, out stubType)) + { + return null; + } + + isStub = true; + } + + container = extension.TrimStart('.'); + } + + var format3DResult = Format3DParser.Parse(path, _options); + + var parsingResult = new EpisodePathParser(_options) + .Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo); + + if (!parsingResult.Success && !isStub) + { + return null; + } + + return new EpisodeInfo(path) + { + Container = container, + IsStub = isStub, + EndingEpisodeNumber = parsingResult.EndingEpisodeNumber, + EpisodeNumber = parsingResult.EpisodeNumber, + SeasonNumber = parsingResult.SeasonNumber, + SeriesName = parsingResult.SeriesName, + StubType = stubType, + Is3D = format3DResult.Is3D, + Format3D = format3DResult.Format3D, + IsByDate = parsingResult.IsByDate, + Day = parsingResult.Day, + Month = parsingResult.Month, + Year = parsingResult.Year + }; + } + } +} diff --git a/Emby.Naming/TV/SeasonPathParser.cs b/Emby.Naming/TV/SeasonPathParser.cs new file mode 100644 index 00000000..72adfb2d --- /dev/null +++ b/Emby.Naming/TV/SeasonPathParser.cs @@ -0,0 +1,174 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text.RegularExpressions; + +namespace Emby.Naming.TV +{ + /// + /// Class to parse season paths. + /// + public static partial class SeasonPathParser + { + private static readonly Regex CleanNameRegex = new(@"[ ._\-\[\]]", RegexOptions.Compiled); + + [GeneratedRegex(@"^\s*((?(?>\d+))(?:st|nd|rd|th|\.)*(?!\s*[Ee]\d+))\s*(?:[[시즌]*|[シーズン]*|[sS](?:eason|æson|aison|taffel|eries|tagione|äsong|eizoen|easong|ezon|ezona|ezóna|ezonul)*|[tT](?:emporada)*|[kK](?:ausi)*|[Сс](?:езон)*)\s*(?.*)$", RegexOptions.IgnoreCase)] + private static partial Regex ProcessPre(); + + [GeneratedRegex(@"^\s*(?:[[시즌]*|[シーズン]*|[sS](?:eason|æson|aison|taffel|eries|tagione|äsong|eizoen|easong|ezon|ezona|ezóna|ezonul)*|[tT](?:emporada)*|[kK](?:ausi)*|[Сс](?:езон)*)\s*(?\d+?)(?=\d{3,4}p|[^\d]|$)(?!\s*[Ee]\d)(?.*)$", RegexOptions.IgnoreCase)] + private static partial Regex ProcessPost(); + + [GeneratedRegex(@"[sS](\d{1,4})(?!\d|[eE]\d)(?=\.|_|-|\[|\]|\s|$)", RegexOptions.None)] + private static partial Regex SeasonPrefix(); + + /// + /// Attempts to parse season number from path. + /// + /// Path to season. + /// Folder name of the parent. + /// Support special aliases when parsing. + /// Support numeric season folders when parsing. + /// Returns object. + public static SeasonPathParserResult Parse(string path, string? parentPath, bool supportSpecialAliases, bool supportNumericSeasonFolders) + { + var result = new SeasonPathParserResult(); + var parentFolderName = parentPath is null ? null : new DirectoryInfo(parentPath).Name; + + var (seasonNumber, isSeasonFolder) = GetSeasonNumberFromPath(path, parentFolderName, supportSpecialAliases, supportNumericSeasonFolders); + + result.SeasonNumber = seasonNumber; + + if (result.SeasonNumber.HasValue) + { + result.Success = true; + result.IsSeasonFolder = isSeasonFolder; + } + + return result; + } + + /// + /// Gets the season number from path. + /// + /// The path. + /// The parent folder name. + /// if set to true [support special aliases]. + /// if set to true [support numeric season folders]. + /// System.Nullable{System.Int32}. + private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPath( + string path, + string? parentFolderName, + bool supportSpecialAliases, + bool supportNumericSeasonFolders) + { + var fileName = Path.GetFileName(path); + + var seasonPrefixMatch = SeasonPrefix().Match(fileName); + if (seasonPrefixMatch.Success && + int.TryParse(seasonPrefixMatch.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) + { + return (val, true); + } + + string filename = CleanNameRegex.Replace(fileName, string.Empty); + + if (parentFolderName is not null) + { + var cleanParent = CleanNameRegex.Replace(parentFolderName, string.Empty); + filename = filename.Replace(cleanParent, string.Empty, StringComparison.OrdinalIgnoreCase); + } + + if (supportSpecialAliases && + (filename.Equals("specials", StringComparison.OrdinalIgnoreCase) || + filename.Equals("extras", StringComparison.OrdinalIgnoreCase))) + { + return (0, true); + } + + if (supportNumericSeasonFolders && + int.TryParse(filename, NumberStyles.Integer, CultureInfo.InvariantCulture, out val)) + { + return (val, true); + } + + var preMatch = ProcessPre().Match(filename); + if (preMatch.Success) + { + return CheckMatch(preMatch); + } + else + { + var postMatch = ProcessPost().Match(filename); + return CheckMatch(postMatch); + } + } + + private static (int? SeasonNumber, bool IsSeasonFolder) CheckMatch(Match match) + { + var numberString = match.Groups["seasonnumber"]; + if (numberString.Success) + { + if (int.TryParse(numberString.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var seasonNumber)) + { + return (seasonNumber, true); + } + } + + return (null, false); + } + + /// + /// Extracts the season number from the second half of the Season folder name (everything after "Season", or "Staffel"). + /// + /// The path. + /// System.Nullable{System.Int32}. + private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan path) + { + var numericStart = -1; + var length = 0; + + var hasOpenParenthesis = false; + var isSeasonFolder = true; + + // Find out where the numbers start, and then keep going until they end + for (var i = 0; i < path.Length; i++) + { + if (char.IsNumber(path[i])) + { + if (!hasOpenParenthesis) + { + if (numericStart == -1) + { + numericStart = i; + } + + length++; + } + } + else if (numericStart != -1) + { + // There's other stuff after the season number, e.g. episode number + isSeasonFolder = false; + break; + } + + var currentChar = path[i]; + if (currentChar == '(') + { + hasOpenParenthesis = true; + } + else if (currentChar == ')') + { + hasOpenParenthesis = false; + } + } + + if (numericStart == -1) + { + return (null, isSeasonFolder); + } + + return (int.Parse(path.Slice(numericStart, length), provider: CultureInfo.InvariantCulture), isSeasonFolder); + } + } +} diff --git a/Emby.Naming/TV/SeasonPathParserResult.cs b/Emby.Naming/TV/SeasonPathParserResult.cs new file mode 100644 index 00000000..b4b6f236 --- /dev/null +++ b/Emby.Naming/TV/SeasonPathParserResult.cs @@ -0,0 +1,26 @@ +namespace Emby.Naming.TV +{ + /// + /// Data object to pass result of . + /// + public class SeasonPathParserResult + { + /// + /// Gets or sets the season number. + /// + /// The season number. + public int? SeasonNumber { get; set; } + + /// + /// Gets or sets a value indicating whether this is success. + /// + /// true if success; otherwise, false. + public bool Success { get; set; } + + /// + /// Gets or sets a value indicating whether "Is season folder". + /// Seems redundant and barely used. + /// + public bool IsSeasonFolder { get; set; } + } +} diff --git a/Emby.Naming/TV/SeriesInfo.cs b/Emby.Naming/TV/SeriesInfo.cs new file mode 100644 index 00000000..5d6cb4bd --- /dev/null +++ b/Emby.Naming/TV/SeriesInfo.cs @@ -0,0 +1,29 @@ +namespace Emby.Naming.TV +{ + /// + /// Holder object for Series information. + /// + public class SeriesInfo + { + /// + /// Initializes a new instance of the class. + /// + /// Path to the file. + public SeriesInfo(string path) + { + Path = path; + } + + /// + /// Gets or sets the path. + /// + /// The path. + public string Path { get; set; } + + /// + /// Gets or sets the name of the series. + /// + /// The name of the series. + public string? Name { get; set; } + } +} diff --git a/Emby.Naming/TV/SeriesPathParser.cs b/Emby.Naming/TV/SeriesPathParser.cs new file mode 100644 index 00000000..94b4b5c8 --- /dev/null +++ b/Emby.Naming/TV/SeriesPathParser.cs @@ -0,0 +1,60 @@ +using Emby.Naming.Common; + +namespace Emby.Naming.TV +{ + /// + /// Used to parse information about series from paths containing more information that only the series name. + /// Uses the same regular expressions as the EpisodePathParser but have different success criteria. + /// + public static class SeriesPathParser + { + /// + /// Parses information about series from path. + /// + /// object containing EpisodeExpressions and MultipleEpisodeExpressions. + /// Path. + /// Returns object. + public static SeriesPathParserResult Parse(NamingOptions options, string path) + { + SeriesPathParserResult? result = null; + + foreach (var expression in options.EpisodeExpressions) + { + var currentResult = Parse(path, expression); + if (currentResult.Success) + { + result = currentResult; + break; + } + } + + if (result is not null) + { + if (!string.IsNullOrEmpty(result.SeriesName)) + { + result.SeriesName = result.SeriesName.Trim(' ', '_', '.', '-'); + } + } + + return result ?? new SeriesPathParserResult(); + } + + private static SeriesPathParserResult Parse(string name, EpisodeExpression expression) + { + var result = new SeriesPathParserResult(); + + var match = expression.Regex.Match(name); + + if (match.Success && match.Groups.Count >= 3) + { + if (expression.IsNamed) + { + result.SeriesName = match.Groups["seriesname"].Value; + result.Success = !string.IsNullOrEmpty(result.SeriesName) && !match.Groups["seasonnumber"].ValueSpan.IsEmpty; + } + } + + return result; + } + } +} diff --git a/Emby.Naming/TV/SeriesPathParserResult.cs b/Emby.Naming/TV/SeriesPathParserResult.cs new file mode 100644 index 00000000..44cd2fdf --- /dev/null +++ b/Emby.Naming/TV/SeriesPathParserResult.cs @@ -0,0 +1,19 @@ +namespace Emby.Naming.TV +{ + /// + /// Holder object for result. + /// + public class SeriesPathParserResult + { + /// + /// Gets or sets the name of the series. + /// + /// The name of the series. + public string? SeriesName { get; set; } + + /// + /// Gets or sets a value indicating whether parsing was successful. + /// + public bool Success { get; set; } + } +} diff --git a/Emby.Naming/TV/SeriesResolver.cs b/Emby.Naming/TV/SeriesResolver.cs new file mode 100644 index 00000000..0b7309ba --- /dev/null +++ b/Emby.Naming/TV/SeriesResolver.cs @@ -0,0 +1,71 @@ +using System.IO; +using System.Text.RegularExpressions; +using Emby.Naming.Common; + +namespace Emby.Naming.TV +{ + /// + /// Used to resolve information about series from path. + /// + public static partial class SeriesResolver + { + /// + /// Regex that matches strings of at least 2 characters separated by a dot or underscore. + /// Used for removing separators between words, i.e turns "The_show" into "The show" while + /// preserving names like "S.H.O.W". + /// + [GeneratedRegex(@"((?[^\._]{2,})[\._]*)|([\._](?[^\._]{2,}))")] + private static partial Regex SeriesNameRegex(); + + /// + /// Regex that matches titles with year in parentheses. Captures the title (which may be + /// numeric) before the year, i.e. turns "1923 (2022)" into "1923". + /// + [GeneratedRegex(@"(?.+?)\s*\(\d{4}\)")] + private static partial Regex TitleWithYearRegex(); + + /// <summary> + /// Resolve information about series from path. + /// </summary> + /// <param name="options"><see cref="NamingOptions"/> object passed to <see cref="SeriesPathParser"/>.</param> + /// <param name="path">Path to series.</param> + /// <returns>SeriesInfo.</returns> + public static SeriesInfo Resolve(NamingOptions options, string path) + { + string seriesName = Path.GetFileName(path); + + // First check if the filename matches a title with year pattern (handles numeric titles) + if (!string.IsNullOrEmpty(seriesName)) + { + var titleWithYearMatch = TitleWithYearRegex().Match(seriesName); + if (titleWithYearMatch.Success) + { + seriesName = titleWithYearMatch.Groups["title"].Value.Trim(); + return new SeriesInfo(path) + { + Name = seriesName + }; + } + } + + SeriesPathParserResult result = SeriesPathParser.Parse(options, path); + if (result.Success) + { + if (!string.IsNullOrEmpty(result.SeriesName)) + { + seriesName = result.SeriesName; + } + } + + if (!string.IsNullOrEmpty(seriesName)) + { + seriesName = SeriesNameRegex().Replace(seriesName, "${a} ${b}").Trim(); + } + + return new SeriesInfo(path) + { + Name = seriesName + }; + } + } +} diff --git a/Emby.Naming/TV/TvParserHelpers.cs b/Emby.Naming/TV/TvParserHelpers.cs new file mode 100644 index 00000000..02991785 --- /dev/null +++ b/Emby.Naming/TV/TvParserHelpers.cs @@ -0,0 +1,44 @@ +using System; +using System.Linq; +using MediaBrowser.Model.Entities; + +namespace Emby.Naming.TV; + +/// <summary> +/// Helper class for TV metadata parsing. +/// </summary> +public static class TvParserHelpers +{ + private static readonly string[] _continuingState = ["Pilot", "Returning Series", "Returning"]; + private static readonly string[] _endedState = ["Cancelled", "Canceled"]; + + /// <summary> + /// Tries to parse a string into <see cref="SeriesStatus"/>. + /// </summary> + /// <param name="status">The status string.</param> + /// <param name="enumValue">The <see cref="SeriesStatus"/>.</param> + /// <returns>Returns true if parsing was successful.</returns> + public static bool TryParseSeriesStatus(string status, out SeriesStatus? enumValue) + { + if (Enum.TryParse(status, true, out SeriesStatus seriesStatus)) + { + enumValue = seriesStatus; + return true; + } + + if (_continuingState.Contains(status, StringComparer.OrdinalIgnoreCase)) + { + enumValue = SeriesStatus.Continuing; + return true; + } + + if (_endedState.Contains(status, StringComparer.OrdinalIgnoreCase)) + { + enumValue = SeriesStatus.Ended; + return true; + } + + enumValue = null; + return false; + } +} diff --git a/Emby.Naming/Video/CleanDateTimeParser.cs b/Emby.Naming/Video/CleanDateTimeParser.cs new file mode 100644 index 00000000..9a6c6e97 --- /dev/null +++ b/Emby.Naming/Video/CleanDateTimeParser.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace Emby.Naming.Video +{ + /// <summary> + /// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />. + /// </summary> + public static class CleanDateTimeParser + { + /// <summary> + /// Attempts to clean the name. + /// </summary> + /// <param name="name">Name of video.</param> + /// <param name="cleanDateTimeRegexes">Optional list of regexes to clean the name.</param> + /// <returns>Returns <see cref="CleanDateTimeResult"/> object.</returns> + public static CleanDateTimeResult Clean(string name, IReadOnlyList<Regex> cleanDateTimeRegexes) + { + CleanDateTimeResult result = new CleanDateTimeResult(name); + if (string.IsNullOrEmpty(name)) + { + return result; + } + + var len = cleanDateTimeRegexes.Count; + for (int i = 0; i < len; i++) + { + if (TryClean(name, cleanDateTimeRegexes[i], ref result)) + { + return result; + } + } + + return result; + } + + private static bool TryClean(string name, Regex expression, ref CleanDateTimeResult result) + { + var match = expression.Match(name); + + if (match.Success + && match.Groups.Count == 5 + && match.Groups[1].Success + && match.Groups[2].Success + && int.TryParse(match.Groups[2].ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year)) + { + result = new CleanDateTimeResult(match.Groups[1].Value.TrimEnd(), year); + return true; + } + + return false; + } + } +} diff --git a/Emby.Naming/Video/CleanDateTimeResult.cs b/Emby.Naming/Video/CleanDateTimeResult.cs new file mode 100644 index 00000000..c675a19d --- /dev/null +++ b/Emby.Naming/Video/CleanDateTimeResult.cs @@ -0,0 +1,31 @@ +namespace Emby.Naming.Video +{ + /// <summary> + /// Holder structure for name and year. + /// </summary> + public readonly struct CleanDateTimeResult + { + /// <summary> + /// Initializes a new instance of the <see cref="CleanDateTimeResult"/> struct. + /// </summary> + /// <param name="name">Name of video.</param> + /// <param name="year">Year of release.</param> + public CleanDateTimeResult(string name, int? year = null) + { + Name = name; + Year = year; + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name { get; } + + /// <summary> + /// Gets the year. + /// </summary> + /// <value>The year.</value> + public int? Year { get; } + } +} diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs new file mode 100644 index 00000000..a336f8fb --- /dev/null +++ b/Emby.Naming/Video/CleanStringParser.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; + +namespace Emby.Naming.Video +{ + /// <summary> + /// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />. + /// </summary> + public static class CleanStringParser + { + /// <summary> + /// Attempts to extract clean name with regular expressions. + /// </summary> + /// <param name="name">Name of file.</param> + /// <param name="expressions">List of regex to parse name and year from.</param> + /// <param name="newName">Parsing result string.</param> + /// <returns>True if parsing was successful.</returns> + public static bool TryClean([NotNullWhen(true)] string? name, IReadOnlyList<Regex> expressions, out string newName) + { + if (string.IsNullOrEmpty(name)) + { + newName = string.Empty; + return false; + } + + // Iteratively apply the regexps to clean the string. + bool cleaned = false; + for (int i = 0; i < expressions.Count; i++) + { + if (TryClean(name, expressions[i], out newName)) + { + cleaned = true; + name = newName; + } + } + + newName = cleaned ? name : string.Empty; + return cleaned; + } + + private static bool TryClean(string name, Regex expression, out string newName) + { + var match = expression.Match(name); + if (match.Success && match.Groups.TryGetValue("cleaned", out var cleaned)) + { + newName = cleaned.Value; + return true; + } + + newName = string.Empty; + return false; + } + } +} diff --git a/Emby.Naming/Video/ExtraResult.cs b/Emby.Naming/Video/ExtraResult.cs new file mode 100644 index 00000000..243fc2b4 --- /dev/null +++ b/Emby.Naming/Video/ExtraResult.cs @@ -0,0 +1,22 @@ +using MediaBrowser.Model.Entities; + +namespace Emby.Naming.Video +{ + /// <summary> + /// Holder object for passing results from ExtraResolver. + /// </summary> + public class ExtraResult + { + /// <summary> + /// Gets or sets the type of the extra. + /// </summary> + /// <value>The type of the extra.</value> + public ExtraType? ExtraType { get; set; } + + /// <summary> + /// Gets or sets the rule. + /// </summary> + /// <value>The rule.</value> + public ExtraRule? Rule { get; set; } + } +} diff --git a/Emby.Naming/Video/ExtraRule.cs b/Emby.Naming/Video/ExtraRule.cs new file mode 100644 index 00000000..e267ac55 --- /dev/null +++ b/Emby.Naming/Video/ExtraRule.cs @@ -0,0 +1,46 @@ +using MediaBrowser.Model.Entities; +using MediaType = Emby.Naming.Common.MediaType; + +namespace Emby.Naming.Video +{ + /// <summary> + /// A rule used to match a file path with an <see cref="MediaBrowser.Model.Entities.ExtraType"/>. + /// </summary> + public class ExtraRule + { + /// <summary> + /// Initializes a new instance of the <see cref="ExtraRule"/> class. + /// </summary> + /// <param name="extraType">Type of extra.</param> + /// <param name="ruleType">Type of rule.</param> + /// <param name="token">Token.</param> + /// <param name="mediaType">Media type.</param> + public ExtraRule(ExtraType extraType, ExtraRuleType ruleType, string token, MediaType mediaType) + { + Token = token; + ExtraType = extraType; + RuleType = ruleType; + MediaType = mediaType; + } + + /// <summary> + /// Gets or sets the token to use for matching against the file path. + /// </summary> + public string Token { get; set; } + + /// <summary> + /// Gets or sets the type of the extra to return when matched. + /// </summary> + public ExtraType ExtraType { get; set; } + + /// <summary> + /// Gets or sets the type of the rule. + /// </summary> + public ExtraRuleType RuleType { get; set; } + + /// <summary> + /// Gets or sets the type of the media to return when matched. + /// </summary> + public MediaType MediaType { get; set; } + } +} diff --git a/Emby.Naming/Video/ExtraRuleResolver.cs b/Emby.Naming/Video/ExtraRuleResolver.cs new file mode 100644 index 00000000..2e0caa61 --- /dev/null +++ b/Emby.Naming/Video/ExtraRuleResolver.cs @@ -0,0 +1,69 @@ +using System; +using System.IO; +using System.Text.RegularExpressions; +using Emby.Naming.Audio; +using Emby.Naming.Common; + +namespace Emby.Naming.Video +{ + /// <summary> + /// Resolve if file is extra for video. + /// </summary> + public static class ExtraRuleResolver + { + private static readonly char[] _digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; + + /// <summary> + /// Attempts to resolve if file is extra. + /// </summary> + /// <param name="path">Path to file.</param> + /// <param name="namingOptions">The naming options.</param> + /// <param name="libraryRoot">Top-level folder for the containing library.</param> + /// <returns>Returns <see cref="ExtraResult"/> object.</returns> + public static ExtraResult GetExtraInfo(string path, NamingOptions namingOptions, string? libraryRoot = "") + { + ExtraResult result = new ExtraResult(); + + bool isAudioFile = AudioFileParser.IsAudioFile(path, namingOptions); + bool isVideoFile = VideoResolver.IsVideoFile(path, namingOptions); + + ReadOnlySpan<char> pathSpan = path.AsSpan(); + ReadOnlySpan<char> fileName = Path.GetFileName(pathSpan); + ReadOnlySpan<char> fileNameWithoutExtension = Path.GetFileNameWithoutExtension(pathSpan); + // Trim the digits from the end of the filename so we can recognize things like -trailer2 + ReadOnlySpan<char> trimmedFileNameWithoutExtension = fileNameWithoutExtension.TrimEnd(_digits); + ReadOnlySpan<char> directoryName = Path.GetFileName(Path.GetDirectoryName(pathSpan)); + string fullDirectory = Path.GetDirectoryName(pathSpan).ToString(); + + foreach (ExtraRule rule in namingOptions.VideoExtraRules) + { + if ((rule.MediaType == MediaType.Audio && !isAudioFile) + || (rule.MediaType == MediaType.Video && !isVideoFile)) + { + continue; + } + + bool isMatch = rule.RuleType switch + { + ExtraRuleType.Filename => fileNameWithoutExtension.Equals(rule.Token, StringComparison.OrdinalIgnoreCase), + ExtraRuleType.Suffix => trimmedFileNameWithoutExtension.EndsWith(rule.Token, StringComparison.OrdinalIgnoreCase), + ExtraRuleType.Regex => Regex.IsMatch(fileName, rule.Token, RegexOptions.IgnoreCase | RegexOptions.Compiled), + ExtraRuleType.DirectoryName => directoryName.Equals(rule.Token, StringComparison.OrdinalIgnoreCase) + && !string.Equals(fullDirectory, libraryRoot, StringComparison.OrdinalIgnoreCase), + _ => false, + }; + + if (!isMatch) + { + continue; + } + + result.ExtraType = rule.ExtraType; + result.Rule = rule; + return result; + } + + return result; + } + } +} diff --git a/Emby.Naming/Video/ExtraRuleType.cs b/Emby.Naming/Video/ExtraRuleType.cs new file mode 100644 index 00000000..32431950 --- /dev/null +++ b/Emby.Naming/Video/ExtraRuleType.cs @@ -0,0 +1,28 @@ +namespace Emby.Naming.Video +{ + /// <summary> + /// Extra rules type to determine against what <see cref="ExtraRule.Token"/> should be matched. + /// </summary> + public enum ExtraRuleType + { + /// <summary> + /// Match <see cref="ExtraRule.Token"/> against a suffix in the file name. + /// </summary> + Suffix = 0, + + /// <summary> + /// Match <see cref="ExtraRule.Token"/> against the file name, excluding the file extension. + /// </summary> + Filename = 1, + + /// <summary> + /// Match <see cref="ExtraRule.Token"/> against the file name, including the file extension. + /// </summary> + Regex = 2, + + /// <summary> + /// Match <see cref="ExtraRule.Token"/> against the name of the directory containing the file. + /// </summary> + DirectoryName = 3 + } +} diff --git a/Emby.Naming/Video/FileStack.cs b/Emby.Naming/Video/FileStack.cs new file mode 100644 index 00000000..4902e672 --- /dev/null +++ b/Emby.Naming/Video/FileStack.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using Jellyfin.Extensions; + +namespace Emby.Naming.Video +{ + /// <summary> + /// Object holding list of files paths with additional information. + /// </summary> + public class FileStack + { + /// <summary> + /// Initializes a new instance of the <see cref="FileStack"/> class. + /// </summary> + /// <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) + { + Name = name; + IsDirectoryStack = isDirectory; + Files = files; + } + + /// <summary> + /// Gets the name of file stack. + /// </summary> + public string Name { get; } + + /// <summary> + /// Gets the list of paths in stack. + /// </summary> + public IReadOnlyList<string> Files { get; } + + /// <summary> + /// Gets a value indicating whether stack is directory stack. + /// </summary> + public bool IsDirectoryStack { get; } + + /// <summary> + /// Helper function to determine if path is in the stack. + /// </summary> + /// <param name="file">Path of desired file.</param> + /// <param name="isDirectory">Requested type of stack.</param> + /// <returns>True if file is in the stack.</returns> + public bool ContainsFile(string file, bool isDirectory) + { + if (string.IsNullOrEmpty(file)) + { + return false; + } + + return IsDirectoryStack == isDirectory && Files.Contains(file, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/Emby.Naming/Video/FileStackRule.cs b/Emby.Naming/Video/FileStackRule.cs new file mode 100644 index 00000000..485097d0 --- /dev/null +++ b/Emby.Naming/Video/FileStackRule.cs @@ -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 | RegexOptions.Compiled); + 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; + } +} diff --git a/Emby.Naming/Video/Format3DParser.cs b/Emby.Naming/Video/Format3DParser.cs new file mode 100644 index 00000000..eb5e71d7 --- /dev/null +++ b/Emby.Naming/Video/Format3DParser.cs @@ -0,0 +1,81 @@ +using System; +using Emby.Naming.Common; + +namespace Emby.Naming.Video +{ + /// <summary> + /// Parse 3D format related flags. + /// </summary> + public static class Format3DParser + { + // Static default result to save on allocation costs. + private static readonly Format3DResult _defaultResult = new(false, null); + + /// <summary> + /// Parse 3D format related flags. + /// </summary> + /// <param name="path">Path to file.</param> + /// <param name="namingOptions">The naming options.</param> + /// <returns>Returns <see cref="Format3DResult"/> object.</returns> + public static Format3DResult Parse(ReadOnlySpan<char> path, NamingOptions namingOptions) + { + int oldLen = namingOptions.VideoFlagDelimiters.Length; + Span<char> delimiters = stackalloc char[oldLen + 1]; + namingOptions.VideoFlagDelimiters.AsSpan().CopyTo(delimiters); + delimiters[oldLen] = ' '; + + return Parse(path, delimiters, namingOptions); + } + + private static Format3DResult Parse(ReadOnlySpan<char> path, ReadOnlySpan<char> delimiters, NamingOptions namingOptions) + { + foreach (var rule in namingOptions.Format3DRules) + { + var result = Parse(path, rule, delimiters); + + if (result.Is3D) + { + return result; + } + } + + return _defaultResult; + } + + private static Format3DResult Parse(ReadOnlySpan<char> path, Format3DRule rule, ReadOnlySpan<char> delimiters) + { + bool is3D = false; + string? format3D = null; + + // If there's no preceding token we just consider it found + var foundPrefix = string.IsNullOrEmpty(rule.PrecedingToken); + while (path.Length > 0) + { + var index = path.IndexOfAny(delimiters); + if (index == -1) + { + index = path.Length - 1; + } + + var currentSlice = path[..index]; + path = path[(index + 1)..]; + + if (!foundPrefix) + { + foundPrefix = currentSlice.Equals(rule.PrecedingToken, StringComparison.OrdinalIgnoreCase); + continue; + } + + is3D = foundPrefix && currentSlice.Equals(rule.Token, StringComparison.OrdinalIgnoreCase); + + if (is3D) + { + format3D = rule.Token; + break; + } + } + + return is3D ? new Format3DResult(true, format3D) : _defaultResult; + } + } +} diff --git a/Emby.Naming/Video/Format3DResult.cs b/Emby.Naming/Video/Format3DResult.cs new file mode 100644 index 00000000..aac959c1 --- /dev/null +++ b/Emby.Naming/Video/Format3DResult.cs @@ -0,0 +1,31 @@ +namespace Emby.Naming.Video +{ + /// <summary> + /// Helper object to return data from <see cref="Format3DParser"/>. + /// </summary> + public class Format3DResult + { + /// <summary> + /// Initializes a new instance of the <see cref="Format3DResult"/> class. + /// </summary> + /// <param name="is3D">A value indicating whether the parsed string contains 3D tokens.</param> + /// <param name="format3D">The 3D format. Value might be null if [is3D] is <c>false</c>.</param> + public Format3DResult(bool is3D, string? format3D) + { + Is3D = is3D; + Format3D = format3D; + } + + /// <summary> + /// Gets a value indicating whether [is3 d]. + /// </summary> + /// <value><c>true</c> if [is3 d]; otherwise, <c>false</c>.</value> + public bool Is3D { get; } + + /// <summary> + /// Gets the format3 d. + /// </summary> + /// <value>The format3 d.</value> + public string? Format3D { get; } + } +} diff --git a/Emby.Naming/Video/Format3DRule.cs b/Emby.Naming/Video/Format3DRule.cs new file mode 100644 index 00000000..e562691d --- /dev/null +++ b/Emby.Naming/Video/Format3DRule.cs @@ -0,0 +1,31 @@ +namespace Emby.Naming.Video +{ + /// <summary> + /// Data holder class for 3D format rule. + /// </summary> + public class Format3DRule + { + /// <summary> + /// Initializes a new instance of the <see cref="Format3DRule"/> class. + /// </summary> + /// <param name="token">Token.</param> + /// <param name="precedingToken">Token present before current token.</param> + public Format3DRule(string token, string? precedingToken = null) + { + Token = token; + PrecedingToken = precedingToken; + } + + /// <summary> + /// Gets or sets the token. + /// </summary> + /// <value>The token.</value> + public string Token { get; set; } + + /// <summary> + /// Gets or sets the preceding token. + /// </summary> + /// <value>The preceding token.</value> + public string? PrecedingToken { get; set; } + } +} diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs new file mode 100644 index 00000000..6a07561a --- /dev/null +++ b/Emby.Naming/Video/StackResolver.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Emby.Naming.AudioBook; +using Emby.Naming.Common; +using MediaBrowser.Model.IO; + +namespace Emby.Naming.Video +{ + /// <summary> + /// Resolve <see cref="FileStack"/> from list of paths. + /// </summary> + public static class StackResolver + { + /// <summary> + /// Resolves only directories from paths. + /// </summary> + /// <param name="files">List of paths.</param> + /// <param name="namingOptions">The naming options.</param> + /// <returns>Enumerable <see cref="FileStack"/> of directories.</returns> + public static IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files, NamingOptions namingOptions) + { + return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }), namingOptions); + } + + /// <summary> + /// Resolves only files from paths. + /// </summary> + /// <param name="files">List of paths.</param> + /// <param name="namingOptions">The naming options.</param> + /// <returns>Enumerable <see cref="FileStack"/> of files.</returns> + public static IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files, NamingOptions namingOptions) + { + return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }), namingOptions); + } + + /// <summary> + /// Resolves audiobooks from paths. + /// </summary> + /// <param name="files">List of paths.</param> + /// <returns>Enumerable <see cref="FileStack"/> of directories.</returns> + public static IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files) + { + var groupedDirectoryFiles = files.GroupBy(file => Path.GetDirectoryName(file.Path)); + + foreach (var directory in groupedDirectoryFiles) + { + if (string.IsNullOrEmpty(directory.Key)) + { + foreach (var file in directory) + { + var stack = new FileStack(Path.GetFileNameWithoutExtension(file.Path), false, new[] { file.Path }); + yield return stack; + } + } + else + { + var stack = new FileStack(Path.GetFileName(directory.Key), false, directory.Select(f => f.Path).ToArray()); + yield return stack; + } + } + } + + /// <summary> + /// Resolves videos from paths. + /// </summary> + /// <param name="files">List of paths.</param> + /// <param name="namingOptions">The naming options.</param> + /// <returns>Enumerable <see cref="FileStack"/> of videos.</returns> + public static IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions) + { + var potentialFiles = files + .Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, namingOptions) || VideoResolver.IsStubFile(i.FullName, namingOptions)) + .OrderBy(i => i.FullName); + + var potentialStacks = new Dictionary<string, StackMetadata>(); + foreach (var file in potentialFiles) + { + var name = file.Name; + if (string.IsNullOrEmpty(name)) + { + name = Path.GetFileName(file.FullName); + } + + for (var i = 0; i < namingOptions.VideoFileStackingRules.Length; i++) + { + var rule = namingOptions.VideoFileStackingRules[i]; + if (!rule.Match(name, out var stackParsingResult)) + { + continue; + } + + var stackName = stackParsingResult.Value.StackName; + var partNumber = stackParsingResult.Value.PartNumber; + var partType = stackParsingResult.Value.PartType; + + if (!potentialStacks.TryGetValue(stackName, out var stackResult)) + { + stackResult = new StackMetadata(file.IsDirectory, rule.IsNumerical, partType); + potentialStacks[stackName] = stackResult; + } + + if (stackResult.Parts.Count > 0) + { + if (stackResult.IsDirectory != file.IsDirectory + || !string.Equals(partType, stackResult.PartType, StringComparison.OrdinalIgnoreCase) + || stackResult.ContainsPart(partNumber)) + { + continue; + } + + if (rule.IsNumerical != stackResult.IsNumerical) + { + break; + } + } + + stackResult.Parts.Add(partNumber, file); + break; + } + } + + foreach (var (fileName, stack) in potentialStacks) + { + if (stack.Parts.Count < 2) + { + continue; + } + + yield return new FileStack(fileName, stack.IsDirectory, stack.Parts.Select(kv => kv.Value.FullName).ToArray()); + } + } + + private sealed class StackMetadata + { + public StackMetadata(bool isDirectory, bool isNumerical, string partType) + { + Parts = new Dictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase); + IsDirectory = isDirectory; + IsNumerical = isNumerical; + PartType = partType; + } + + 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); + } + } +} diff --git a/Emby.Naming/Video/StubResolver.cs b/Emby.Naming/Video/StubResolver.cs new file mode 100644 index 00000000..4b9df19b --- /dev/null +++ b/Emby.Naming/Video/StubResolver.cs @@ -0,0 +1,50 @@ +using System; +using System.IO; +using Emby.Naming.Common; +using Jellyfin.Extensions; + +namespace Emby.Naming.Video +{ + /// <summary> + /// Resolve if file is stub (.disc). + /// </summary> + public static class StubResolver + { + /// <summary> + /// Tries to resolve if file is stub (.disc). + /// </summary> + /// <param name="path">Path to file.</param> + /// <param name="options">NamingOptions containing StubFileExtensions and StubTypes.</param> + /// <param name="stubType">Stub type.</param> + /// <returns>True if file is a stub.</returns> + public static bool TryResolveFile(string path, NamingOptions options, out string? stubType) + { + stubType = default; + + if (string.IsNullOrEmpty(path)) + { + return false; + } + + var extension = Path.GetExtension(path.AsSpan()); + + if (!options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + var token = Path.GetExtension(Path.GetFileNameWithoutExtension(path.AsSpan())).TrimStart('.'); + + foreach (var rule in options.StubTypes) + { + if (token.Equals(rule.Token, StringComparison.OrdinalIgnoreCase)) + { + stubType = rule.StubType; + return true; + } + } + + return true; + } + } +} diff --git a/Emby.Naming/Video/StubTypeRule.cs b/Emby.Naming/Video/StubTypeRule.cs new file mode 100644 index 00000000..dfb3ac01 --- /dev/null +++ b/Emby.Naming/Video/StubTypeRule.cs @@ -0,0 +1,31 @@ +namespace Emby.Naming.Video +{ + /// <summary> + /// Data class holding information about Stub type rule. + /// </summary> + public class StubTypeRule + { + /// <summary> + /// Initializes a new instance of the <see cref="StubTypeRule"/> class. + /// </summary> + /// <param name="token">Token.</param> + /// <param name="stubType">Stub type.</param> + public StubTypeRule(string token, string stubType) + { + Token = token; + StubType = stubType; + } + + /// <summary> + /// Gets or sets the token. + /// </summary> + /// <value>The token.</value> + public string Token { get; set; } + + /// <summary> + /// Gets or sets the type of the stub. + /// </summary> + /// <value>The type of the stub.</value> + public string StubType { get; set; } + } +} diff --git a/Emby.Naming/Video/VideoFileInfo.cs b/Emby.Naming/Video/VideoFileInfo.cs new file mode 100644 index 00000000..481773ff --- /dev/null +++ b/Emby.Naming/Video/VideoFileInfo.cs @@ -0,0 +1,120 @@ +using System; +using MediaBrowser.Model.Entities; + +namespace Emby.Naming.Video +{ + /// <summary> + /// Represents a single video file. + /// </summary> + public class VideoFileInfo + { + /// <summary> + /// Initializes a new instance of the <see cref="VideoFileInfo"/> class. + /// </summary> + /// <param name="name">Name of file.</param> + /// <param name="path">Path to the file.</param> + /// <param name="container">Container type.</param> + /// <param name="year">Year of release.</param> + /// <param name="extraType">Extra type.</param> + /// <param name="extraRule">Extra rule.</param> + /// <param name="format3D">Format 3D.</param> + /// <param name="is3D">Is 3D.</param> + /// <param name="isStub">Is Stub.</param> + /// <param name="stubType">Stub type.</param> + /// <param name="isDirectory">Is directory.</param> + public VideoFileInfo(string name, string path, string? container, int? year = default, ExtraType? extraType = default, ExtraRule? extraRule = default, string? format3D = default, bool is3D = default, bool isStub = default, string? stubType = default, bool isDirectory = default) + { + Path = path; + Container = container; + Name = name; + Year = year; + ExtraType = extraType; + ExtraRule = extraRule; + Format3D = format3D; + Is3D = is3D; + IsStub = isStub; + StubType = stubType; + IsDirectory = isDirectory; + } + + /// <summary> + /// Gets or sets the path. + /// </summary> + /// <value>The path.</value> + public string Path { get; set; } + + /// <summary> + /// Gets or sets the container. + /// </summary> + /// <value>The container.</value> + public string? Container { get; set; } + + /// <summary> + /// Gets or sets the name. + /// </summary> + /// <value>The name.</value> + public string Name { get; set; } + + /// <summary> + /// Gets or sets the year. + /// </summary> + /// <value>The year.</value> + public int? Year { get; set; } + + /// <summary> + /// Gets or sets the type of the extra, e.g. trailer, theme song, behind the scenes, etc. + /// </summary> + /// <value>The type of the extra.</value> + public ExtraType? ExtraType { get; set; } + + /// <summary> + /// Gets or sets the extra rule. + /// </summary> + /// <value>The extra rule.</value> + public ExtraRule? ExtraRule { get; set; } + + /// <summary> + /// Gets or sets the format3 d. + /// </summary> + /// <value>The format3 d.</value> + public string? Format3D { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether [is3 d]. + /// </summary> + /// <value><c>true</c> if [is3 d]; otherwise, <c>false</c>.</value> + public bool Is3D { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is stub. + /// </summary> + /// <value><c>true</c> if this instance is stub; otherwise, <c>false</c>.</value> + public bool IsStub { get; set; } + + /// <summary> + /// Gets or sets the type of the stub. + /// </summary> + /// <value>The type of the stub.</value> + public string? StubType { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is a directory. + /// </summary> + /// <value>The type.</value> + public bool IsDirectory { get; set; } + + /// <summary> + /// Gets the file name without extension. + /// </summary> + /// <value>The file name without extension.</value> + public ReadOnlySpan<char> FileNameWithoutExtension => !IsDirectory + ? System.IO.Path.GetFileNameWithoutExtension(Path.AsSpan()) + : System.IO.Path.GetFileName(Path.AsSpan()); + + /// <inheritdoc /> + public override string ToString() + { + return "VideoFileInfo(Name: '" + Name + "')"; + } + } +} diff --git a/Emby.Naming/Video/VideoInfo.cs b/Emby.Naming/Video/VideoInfo.cs new file mode 100644 index 00000000..8847ee9b --- /dev/null +++ b/Emby.Naming/Video/VideoInfo.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Model.Entities; + +namespace Emby.Naming.Video +{ + /// <summary> + /// Represents a complete video, including all parts and subtitles. + /// </summary> + public class VideoInfo + { + /// <summary> + /// Initializes a new instance of the <see cref="VideoInfo" /> class. + /// </summary> + /// <param name="name">The name.</param> + public VideoInfo(string? name) + { + Name = name; + + Files = Array.Empty<VideoFileInfo>(); + AlternateVersions = Array.Empty<VideoFileInfo>(); + } + + /// <summary> + /// Gets or sets the name. + /// </summary> + /// <value>The name.</value> + public string? Name { get; set; } + + /// <summary> + /// Gets or sets the year. + /// </summary> + /// <value>The year.</value> + public int? Year { get; set; } + + /// <summary> + /// Gets or sets the files. + /// </summary> + /// <value>The files.</value> + public IReadOnlyList<VideoFileInfo> Files { get; set; } + + /// <summary> + /// Gets or sets the alternate versions. + /// </summary> + /// <value>The alternate versions.</value> + public IReadOnlyList<VideoFileInfo> AlternateVersions { get; set; } + + /// <summary> + /// Gets or sets the extra type. + /// </summary> + public ExtraType? ExtraType { get; set; } + } +} diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs new file mode 100644 index 00000000..4247fea0 --- /dev/null +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using Emby.Naming.Common; +using Jellyfin.Extensions; +using MediaBrowser.Model.IO; + +namespace Emby.Naming.Video +{ + /// <summary> + /// Resolves alternative versions and extras from list of video files. + /// </summary> + public static partial class VideoListResolver + { + [GeneratedRegex("[0-9]{2}[0-9]+[ip]", RegexOptions.IgnoreCase)] + private static partial Regex ResolutionRegex(); + + [GeneratedRegex(@"^\[([^]]*)\]")] + private static partial Regex CheckMultiVersionRegex(); + + /// <summary> + /// Resolves alternative versions and extras from list of video files. + /// </summary> + /// <param name="videoInfos">List of related video files.</param> + /// <param name="namingOptions">The naming options.</param> + /// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param> + /// <param name="parseName">Whether to parse the name or use the filename.</param> + /// <param name="libraryRoot">Top-level folder for the containing library.</param> + /// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files together when related.</returns> + public static IReadOnlyList<VideoInfo> Resolve(IReadOnlyList<VideoFileInfo> videoInfos, NamingOptions namingOptions, bool supportMultiVersion = true, bool parseName = true, string? libraryRoot = "") + { + // Filter out all extras, otherwise they could cause stacks to not be resolved + // See the unit test TestStackedWithTrailer + var nonExtras = videoInfos + .Where(i => i.ExtraType is null) + .Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory }); + + var stackResult = StackResolver.Resolve(nonExtras, namingOptions).ToList(); + + var remainingFiles = new List<VideoFileInfo>(); + var standaloneMedia = new List<VideoFileInfo>(); + + for (var i = 0; i < videoInfos.Count; i++) + { + var current = videoInfos[i]; + if (stackResult.Any(s => s.ContainsFile(current.Path, current.IsDirectory))) + { + continue; + } + + if (current.ExtraType is null) + { + standaloneMedia.Add(current); + } + else + { + remainingFiles.Add(current); + } + } + + var list = new List<VideoInfo>(); + + foreach (var stack in stackResult) + { + var info = new VideoInfo(stack.Name) + { + Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions, parseName, libraryRoot)) + .OfType<VideoFileInfo>() + .ToList() + }; + + info.Year = info.Files[0].Year; + list.Add(info); + } + + foreach (var media in standaloneMedia) + { + var info = new VideoInfo(media.Name) { Files = new[] { media } }; + + info.Year = info.Files[0].Year; + list.Add(info); + } + + if (supportMultiVersion) + { + list = GetVideosGroupedByVersion(list, namingOptions); + } + + // Whatever files are left, just add them + list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name) + { + Files = new[] { i }, + Year = i.Year, + ExtraType = i.ExtraType + })); + + return list; + } + + private static List<VideoInfo> GetVideosGroupedByVersion(List<VideoInfo> videos, NamingOptions namingOptions) + { + if (videos.Count == 0) + { + return videos; + } + + var folderName = Path.GetFileName(Path.GetDirectoryName(videos[0].Files[0].Path.AsSpan())); + + if (folderName.Length <= 1 || !HaveSameYear(videos)) + { + return videos; + } + + // Cannot use Span inside local functions and delegates thus we cannot use LINQ here nor merge with the above [if] + VideoInfo? primary = null; + for (var i = 0; i < videos.Count; i++) + { + var video = videos[i]; + if (video.ExtraType is not null) + { + continue; + } + + if (!IsEligibleForMultiVersion(folderName, video.Files[0].FileNameWithoutExtension, namingOptions)) + { + return videos; + } + + if (folderName.Equals(video.Files[0].FileNameWithoutExtension, StringComparison.Ordinal)) + { + primary = video; + } + } + + if (videos.Count > 1) + { + var groups = videos + .Select(x => (filename: x.Files[0].FileNameWithoutExtension.ToString(), value: x)) + .Select(x => (x.filename, resolutionMatch: ResolutionRegex().Match(x.filename), x.value)) + .GroupBy(x => x.resolutionMatch.Success) + .ToList(); + + videos.Clear(); + + StringComparer comparer = StringComparer.Create(CultureInfo.InvariantCulture, CompareOptions.NumericOrdering); + foreach (var group in groups) + { + if (group.Key) + { + videos.InsertRange(0, group + .OrderByDescending(x => x.resolutionMatch.Value, comparer) + .ThenBy(x => x.filename, comparer) + .Select(x => x.value)); + } + else + { + videos.AddRange(group.OrderBy(x => x.filename, comparer).Select(x => x.value)); + } + } + } + + primary ??= videos[0]; + videos.Remove(primary); + + var list = new List<VideoInfo> + { + primary + }; + + list[0].AlternateVersions = videos.Select(x => x.Files[0]).ToArray(); + list[0].Name = folderName.ToString(); + + return list; + } + + private static bool HaveSameYear(IReadOnlyList<VideoInfo> videos) + { + if (videos.Count == 1) + { + return true; + } + + var firstYear = videos[0].Year ?? -1; + for (var i = 1; i < videos.Count; i++) + { + if ((videos[i].Year ?? -1) != firstYear) + { + return false; + } + } + + return true; + } + + private static bool IsEligibleForMultiVersion(ReadOnlySpan<char> folderName, ReadOnlySpan<char> testFilename, NamingOptions namingOptions) + { + if (!testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + // Remove the folder name before cleaning as we don't care about cleaning that part + if (folderName.Length <= testFilename.Length) + { + testFilename = testFilename[folderName.Length..].Trim(); + } + + // There are no span overloads for regex unfortunately + if (CleanStringParser.TryClean(testFilename.ToString(), namingOptions.CleanStringRegexes, out var cleanName)) + { + testFilename = cleanName.AsSpan().Trim(); + } + + // The CleanStringParser should have removed common keywords etc. + return testFilename.IsEmpty + || testFilename[0] == '-' + || CheckMultiVersionRegex().IsMatch(testFilename); + } + } +} diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs new file mode 100644 index 00000000..afbf6f8f --- /dev/null +++ b/Emby.Naming/Video/VideoResolver.cs @@ -0,0 +1,160 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using Emby.Naming.Common; +using Jellyfin.Extensions; + +namespace Emby.Naming.Video +{ + /// <summary> + /// Resolves <see cref="VideoFileInfo"/> from file path. + /// </summary> + public static class VideoResolver + { + /// <summary> + /// Resolves the directory. + /// </summary> + /// <param name="path">The path.</param> + /// <param name="namingOptions">The naming options.</param> + /// <param name="parseName">Whether to parse the name or use the filename.</param> + /// <param name="libraryRoot">Top-level folder for the containing library.</param> + /// <returns>VideoFileInfo.</returns> + public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions, bool parseName = true, string? libraryRoot = "") + { + return Resolve(path, true, namingOptions, parseName, libraryRoot); + } + + /// <summary> + /// Resolves the file. + /// </summary> + /// <param name="path">The path.</param> + /// <param name="namingOptions">The naming options.</param> + /// <param name="libraryRoot">Top-level folder for the containing library.</param> + /// <returns>VideoFileInfo.</returns> + public static VideoFileInfo? ResolveFile(string? path, NamingOptions namingOptions, string? libraryRoot = "") + { + return Resolve(path, false, namingOptions, libraryRoot: libraryRoot); + } + + /// <summary> + /// Resolves the specified path. + /// </summary> + /// <param name="path">The path.</param> + /// <param name="isDirectory">if set to <c>true</c> [is folder].</param> + /// <param name="namingOptions">The naming options.</param> + /// <param name="parseName">Whether or not the name should be parsed for info.</param> + /// <param name="libraryRoot">Top-level folder for the containing library.</param> + /// <returns>VideoFileInfo.</returns> + /// <exception cref="ArgumentNullException"><c>path</c> is <c>null</c>.</exception> + public static VideoFileInfo? Resolve(string? path, bool isDirectory, NamingOptions namingOptions, bool parseName = true, string? libraryRoot = "") + { + if (string.IsNullOrEmpty(path)) + { + return null; + } + + bool isStub = false; + ReadOnlySpan<char> container = ReadOnlySpan<char>.Empty; + string? stubType = null; + + if (!isDirectory) + { + var extension = Path.GetExtension(path.AsSpan()); + + // Check supported extensions + if (!namingOptions.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) + { + // It's not supported. Check stub extensions + if (!StubResolver.TryResolveFile(path, namingOptions, out stubType)) + { + return null; + } + + isStub = true; + } + + container = extension.TrimStart('.'); + } + + var format3DResult = Format3DParser.Parse(path, namingOptions); + + var extraResult = ExtraRuleResolver.GetExtraInfo(path, namingOptions, libraryRoot); + + var name = Path.GetFileNameWithoutExtension(path); + + int? year = null; + + if (parseName) + { + var cleanDateTimeResult = CleanDateTime(name, namingOptions); + name = cleanDateTimeResult.Name; + year = cleanDateTimeResult.Year; + + if (TryCleanString(name, namingOptions, out var newName)) + { + name = newName; + } + } + + return new VideoFileInfo( + path: path, + container: container.IsEmpty ? null : container.ToString(), + isStub: isStub, + name: name, + year: year, + stubType: stubType, + is3D: format3DResult.Is3D, + format3D: format3DResult.Format3D, + extraType: extraResult.ExtraType, + isDirectory: isDirectory, + extraRule: extraResult.Rule); + } + + /// <summary> + /// Determines if path is video file based on extension. + /// </summary> + /// <param name="path">Path to file.</param> + /// <param name="namingOptions">The naming options.</param> + /// <returns>True if is video file.</returns> + public static bool IsVideoFile(string path, NamingOptions namingOptions) + { + var extension = Path.GetExtension(path.AsSpan()); + return namingOptions.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase); + } + + /// <summary> + /// Determines if path is video file stub based on extension. + /// </summary> + /// <param name="path">Path to file.</param> + /// <param name="namingOptions">The naming options.</param> + /// <returns>True if is video file stub.</returns> + public static bool IsStubFile(string path, NamingOptions namingOptions) + { + var extension = Path.GetExtension(path.AsSpan()); + return namingOptions.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase); + } + + /// <summary> + /// Tries to clean name of clutter. + /// </summary> + /// <param name="name">Raw name.</param> + /// <param name="namingOptions">The naming options.</param> + /// <param name="newName">Clean name.</param> + /// <returns>True if cleaning of name was successful.</returns> + public static bool TryCleanString([NotNullWhen(true)] string? name, NamingOptions namingOptions, out string newName) + { + return CleanStringParser.TryClean(name, namingOptions.CleanStringRegexes, out newName); + } + + /// <summary> + /// Tries to get name and year from raw name. + /// </summary> + /// <param name="name">Raw name.</param> + /// <param name="namingOptions">The naming options.</param> + /// <returns>Returns <see cref="CleanDateTimeResult"/> with name and optional year.</returns> + public static CleanDateTimeResult CleanDateTime(string name, NamingOptions namingOptions) + { + return CleanDateTimeParser.Clean(name, namingOptions.CleanDateTimeRegexes); + } + } +} diff --git a/Emby.Naming/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs b/Emby.Naming/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs new file mode 100644 index 00000000..925b1351 --- /dev/null +++ b/Emby.Naming/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// <autogenerated /> +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")] diff --git a/Emby.Naming/obj/Debug/net10.0/Emby.Naming.GeneratedMSBuildEditorConfig.editorconfig b/Emby.Naming/obj/Debug/net10.0/Emby.Naming.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 00000000..fd12da55 --- /dev/null +++ b/Emby.Naming/obj/Debug/net10.0/Emby.Naming.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,27 @@ +is_global = true +build_property.TargetFramework = net10.0 +build_property.TargetFramework = net10.0 +build_property.TargetFrameworkIdentifier = .NETCoreApp +build_property.TargetFrameworkIdentifier = .NETCoreApp +build_property.TargetFrameworkVersion = v10.0 +build_property.TargetFrameworkVersion = v10.0 +build_property.TargetPlatformMinVersion = +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = Emby.Naming +build_property.ProjectDir = /srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 10.0 +build_property.EnableCodeStyleSeverity = diff --git a/Emby.Naming/obj/Debug/net10.0/Emby.Naming.assets.cache b/Emby.Naming/obj/Debug/net10.0/Emby.Naming.assets.cache new file mode 100644 index 0000000000000000000000000000000000000000..4b14aa09a0df3d0dae9dad058d728377f4dcfd4f GIT binary patch literal 14005 zcmd5@&37Ed5f?!p$cY8m4w1nQ7DTZDYgXTuNPq~*5^F4@ST;!@U!&Q!G-h^YGBb<4 zB-gyWyd07|-Xq5xa^@DF_z#jBe*jnh1J0@6&h&QeY|n>A8(H&SzuoDruKIO#cXd@) z@6NkhYu_0f8v61Z@$B^Pz8?P9quK{w{O8}FPky!e`6KV|SN`zD*PmSbZ+qj>YePe) zh^|pP@S39b;J)XIux7ZeeG%1s)B4EVr+2$yhrSn@dyX(&(`kPqg0Qw|%o-~-_1^Fz zml|1Nt-jGEUz6lU!*RYvg77>+1>c{f^4BMFB{yv=@IvoiWLRD!79QH6v1hujXqdjO z#4j3igh-2LyUS#qm~<e4$=e+!Yrd@oIh=qTq0i4AAISCH12gasf%PwCu?jb`qxQYP zY>Ee7@R8*O!cgj*o2B7hC{XA14vS66VjV2B${bCUIYyuVC=KvoB;3&U+|Y2m{e9cr zH_W{-3QQ|PWi1OLO&F)`E*guNi)!qzD|R;%c3Z*~ff;#0$fxI14D8=3fc5;N;(IUV zVC;IWz!C{c>N7_<&QN}syM{s$*rY%?wzXMfZYg7>?8c;W47og52`t)gQ~6WAR>Gzk zL{eO6G+PeMw1YsH4H`pPUz;=LjD<Pv4BG_*2_iJMdl<-i4i2Uc;7(I{kgtrW?Fh^B z)l@fHZWzI|5u$mT^h-<2dQfP&PAw&X(11DFv>%F|=`)dFd73_($A^X4SB`){44t8} zm5Zcdn^s^)HZ2&kqqK4yd#^^;e3j<OM)x;p95x%zGgP4QXQ_bySt>iZGHk20=+>5L z_8GG?rsE%&#saB2kwin_Hq4+wBT%ca-(9@Lg`Fej5cXXv5cV7uw^*1#w!#e^JBk<i zA!Og_X~>~`k3@4Qlk~ot3+4X2Tf@AXm0ew?uQ%p<I(R<e`aFH2gD+6=a=EIFQDeuY zX-sqrL&*)H#%i;Xy^vsmtp=8hRQ{a5;B&!fjdpotYs}>Hf%tw3=8N?H$D_augk!>R zo6h0Yce~q`L%fuNI7ROTrjuz>ZC93Q9c1l(8H!Ym*OSIsdVDDb@@0Deec7p}#7K-y z(e#3Lfd*AGiEg{oj4SU4DO}U^{%Ps)RT3=Ey=U*YXq(3ds&gQURoKHKOOZcJ0lrM{ z<tL;9+z`HS8^X2PwtHW6e4Wzj+~~Hg%eGvnS5gpPq4$4Q(5VJuHO=bqX6g6U6nKhU z3iem+VoF@e(khj{BD0WJQ%Gj$y~?^ypj1<x#FeEk?#0548`ad6a%KzHigaC>y11tb zBsD7G;Vq0G*iCX#KBjmy@%#(H>~x#(RWM8Pv4zVGZjQdck&DOo9H(7dF_w)bzKQRk znsc5{I4{sAdb&s@-|^~jrr0(Rz3f+L$Bx-|f-0uXrId_iddGtv9ibO~V-Fng@LmiA zJ>$*MLl%w#Cbzrs`C91T5>XsS`aTHoz&kpn+)z}FbbLg7I~RM4y&hZqgXS0lKRij! z7d~H6atqD{Kz&hBYr<YoyqZg4@P!KU%LU}}<OH-=@@bX(0b<rK7W2xTHVxxt##(Jc zk~-d(!Ie9Zj4pF)L>mrUwb2-T5HD$gXo&kXc|qGUF_2)b*ND!7f?CfmF>D}sQ7caw z8_<k&RxiGtQtKG6Pp4vR)J>NPctLq)w=W7s^$Y#g#@!^W#qT)8dR})d7oXzc=WA53 z8ds<w$XliII+Y($`7xC@sJuyKR&~B$)gY9lBRR<&2WnuBcOCP$sQ$T>a+0$O$pU8( zV1Y9T9gCk({nb*sOimvpW1Js=G0qQkjMu1ss$a+PiNVaCh)f(CNN#T@+}1O=ZBRW_ zeKB{_A%e~(l5c`bM(z)r-P2bdxeqpCXF_<yew>jygTN#8iV-^FcAiE98zdt1V~xxi z1|FH8l!%<c;}LoJz(;cmJ)lUOLA#WU9$daEp1O=~O?DVj+6$F?Dc^Yw2JR&W?y*JQ z3=faIPhP~$fbxjD{Df1Eh$f6JJ*YarghAtx_Q{U08E771=bv0RyB@nx>P*7f(khhx z7el}!>niIsfiS>`nt|gHbp`cQjiea@9!Vd21kG^p2>J*yC*wHqn3M5N$DD}F2jWyw zRF1?Z9`00uKhh!Bsccf&qVf)vI+Yt#2J3V&5Zvhk2&74LAl{|=<8;axMDCOU;$#NI zn^a%XX=Bp3(*|istLPN6P4&m>)G>_QsRKr&TXYz4WN}iQJ_esVeSnWNj1K;LR9}9N z{{K0J4EiO41eac(15VL*9rPWlud*2p-f3juxzh-}BW<Gt|5K`eY)&P^%biN#ozB2} zo9a)V)5$u*olbP*az;mXslNP#S6<Yhol*v!JEee*^p9@N+@boD?X<E^ai<lXLRv`I zsk>BP!E3AP)G~zJsRcr$i*yLzr}`>(X7Elg1J9jafJYih2mWVNUqQW9onnTNJH<eV z^pXzY&#C^{oo0rUJI%m}w380!FR1<qPBoJ_Pn@v9@pP0<;s->N?+}+yQ%Q%IJKg9l z(o{OxA5uLY963)LM(u{6oZ3CxH8#x1?0qw=&=0)(=n<Y@Qo;9MQL(6H7+AgZduAy5 zYm9o+cSMtJ&2(-AgvAsoxxY^2{)Rr6(sEV8Me$X2DNeslIH3o4Oezz%T3aI8iErN4 z8|QR~_GdZG`*atVZZ?c`uH&*qN5SFzXnZ(dHVHKvr(79-)+-sv^8>EjBuY95G8$h= zESClujV})7r98&FH~CoFkTgU$+fMJ8(Lvo!6ns8OA@|>NixZ5`q_3-8NE&Fbt7%h_ zuVuFAO<8vG$ds8J>D(1NleA)Y#qtx7Gf9JTcfAggs(igXV?O@q4p0AS?wqNt<w|)O zsu3jU=cDexbu~kTa=dh{AfrrzbfGkNb<ydt3DviXWn?B})1#q!tvScc8jajWkWejy zd%dt<8jL+TV93_no5V6*EfgV57;!3|DUvun-;2et+c8B+WO>mfy`a@7sDv{V5GR~R zI+s+=CH;l<h_lZ`!1B=2?DgL=8NNk-2N+BCl(Y2^o26~pk<;D~v@46o$foIp;;Gvr z^qh}HWB5+c62q{m*K(7Y+5aHf&TXd-nypg*|5(W7roRba&u?>x%j}x${6uFf>ZXX_ z%$L=7IX#tO9$yPX5#4F~Vk~HpNo|U8x{=<#=~<>T9MMpm*$`o5yB*hNx3!0XnNT-p zFi*z2x2q?0H)lXHmfTLBQmKB;l^0MN`4oK%B%i$3w$CtPHR-dgG<RhBsg>1T&~weT zoL9ANbXJK}vRaxrt({BSmeSY|YrRG!K{<BJ0_iulUaK<RH6Ih|8Vg4LbdRGxMpkN$ Z9G9t{S~%coce_JL{{T4KrBsgO{{Ug=!D;{i literal 0 HcmV?d00001 diff --git a/Emby.Naming/obj/Debug/net10.0/Emby.Naming.csproj.AssemblyReference.cache b/Emby.Naming/obj/Debug/net10.0/Emby.Naming.csproj.AssemblyReference.cache new file mode 100644 index 0000000000000000000000000000000000000000..63096df18f944b049a16d14e18942da4dd315e42 GIT binary patch literal 3805 zcmZQ$<YZuAV072d$j?pHFVD))OD)#d%PUP!EzvJXOwLYB2l7)g6O)TFOEQy-^-c5) z^-T10GL!W4QcEoL4D?;#a(XE_IgE@z(-@Z3T73z+$HB<R$c2z*WC2+O#2^|OFxCTk zEDaDzD9PxE(5RyjmReMtnV)A1wp`CZN5Q!?r=+wf)iy7+w4^98M@J!`G$|)D**mo| zBtJVf&o-|#CkJRK7%&Fl^;>3gsY#x`nE}vW2D*tk1sREY#-?U49~PG+=A|SSr5FKy z>ggP6;)n2L<wS#y>o-B3WChDG+Q4jtSq!65{KvQeB&-7Q@f0AFjS0dJ3U)LEg$*MR zUesVrn7z5i>EtR^=Y0xG60c~6d}T~(p0rf@YEj6B<p*W!;$s^_x1BE5kzuIFI-}9V zv|$Rra+TNgDP88<3eANRb;VB|=Kr}zphDSXx$Ze-wc{BXaW#)t2b*4#QF^B2RsHU2 z*XFakl(+QO?cNtNWzU3&yuNj&eKHecIrPnbCO%^10J`b@#+p8VK22#xPq6<G(EtoA zFeDrgscA`u=E)YRCTRw#MwW(2KvThhF^_PZ=mBFkuQ(^OB(*59B)^D+C<;N*h={Sv zpN*g~CIX2|RE5Jg0wb__m#Xn;i8ns8jPmq>IS81Cpaq8+km+TFl$rQBvzI_J6Ejep z5tJp60k~8F3ZNDhL}jJ9lGi7>_N>fXd9ur+(>jBhN$72_$g~-^Ql4JPJ!5(8fQiwG z9$j6Q2NU=tuQFP!UVWhW-JhIGa`QiFs4y<sBJ%9yiIvyCd0c8exbVy3mtj)zjZ7~T zIv;9EmM>frT>SIuTqO?gC1z)A>vP=26+@2ff1dPr!@oC&RFC^`8r=4KcxeGRD}A2g zer@{7%=L_JK;Iz(9vmjblxJzimL?|VDQ1@DiI!%TiD|H~Va&%H?zx%CMft_~X(f88 zc_o=8m1#wZxvAy(McK*uMXCCRpiFEGixLn|-xsdI6|BJxrokDg0g;goDJa;)fXjDT zygC@Upblk*(iFtwXDdw>|CQGrPHtATn*6k;WlPZsKkhC*sV9BuD}Q{uH#ffCHfW9V zldhu@j34hz<=QtvcCoM4JnL1rMWn=!*L^;kdN=MVtM|n7$-8Z`cGwrKwp#zvmjCb} zlkPJA^&2)GD7Jkn61i@*TLg2<{<$Sb9yO+HKl{l2$HZOioicOVwJz-j$K%?al@^+J z*Cj9};`KKoh9O~0OcW=kBw3nU8e14yrlcgMni;^OxRa6mIMz!{Dh3v#$tA$rvzYV< zcSO>Sh<$l`K4|P~;SB{nWbMN@DdZ6!2vkiMt(2sTqSTy3P@+i8Aw5+D!Sy22gw`re zXqwQ#9~=nH!#73b5E}$kO%Jwsi;05#oSaI13q3PEQ&@=)s)PeT!iZ>$eKoOH3tX_X zf@Q#k9;_q+(G(Qmq8yTsJ^iPvSy}D9*cZp+zQHet@1D!Dqp|^8U9T{odtI~J=wk2g zJ$Ge0R@7D(D`X30KQ!5pFvo0_&1Tt4PwFg<CJShA?zmnrsc+bk*yZ#r@Sxv*)*oND zXRVZW7IWHUGW~7Sw%FuKnOloxothS1JH7hwKX%t`+cJ3^YgT_-Xfg-vmlKQsAO7(} z^$ep2*nfx!upqWcn{1J2oR*esoMw?`VPu+|3=16sT_|w)>w)WClt=*D3ylQSDi#qF TT#z^dNe|!X@I`o%ig5w}H5SN* literal 0 HcmV?d00001 diff --git a/Emby.Naming/obj/Emby.Naming.csproj.nuget.dgspec.json b/Emby.Naming/obj/Emby.Naming.csproj.nuget.dgspec.json new file mode 100644 index 00000000..464237c3 --- /dev/null +++ b/Emby.Naming/obj/Emby.Naming.csproj.nuget.dgspec.json @@ -0,0 +1,3330 @@ +{ + "format": 1, + "restore": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj": {} + }, + "projects": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj", + "projectName": "Jellyfin.Naming", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "projectName": "Jellyfin.Data", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Logging": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj", + "projectName": "Jellyfin.Common", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.AspNetCore": "(,10.0.32767]", + "Microsoft.AspNetCore.Antiforgery": "(,10.0.32767]", + "Microsoft.AspNetCore.App": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.BearerToken": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Cookies": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.OAuth": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization.Policy": "(,10.0.32767]", + "Microsoft.AspNetCore.Components": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Endpoints": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Forms": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Server": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Web": "(,10.0.32767]", + "Microsoft.AspNetCore.Connections.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.CookiePolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.Internal": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.AspNetCore.HostFiltering": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Html.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Features": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Results": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpLogging": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpOverrides": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpsPolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Identity": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Metadata": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ApiExplorer": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.DataAnnotations": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Xml": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.RazorPages": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.TagHelpers": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ViewFeatures": "(,10.0.32767]", + "Microsoft.AspNetCore.OutputCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.RateLimiting": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor.Runtime": "(,10.0.32767]", + "Microsoft.AspNetCore.RequestDecompression": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCompression": "(,10.0.32767]", + "Microsoft.AspNetCore.Rewrite": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.HttpSys": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IIS": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IISIntegration": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets": "(,10.0.32767]", + "Microsoft.AspNetCore.Session": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticAssets": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticFiles": "(,10.0.32767]", + "Microsoft.AspNetCore.WebSockets": "(,10.0.32767]", + "Microsoft.AspNetCore.WebUtilities": "(,10.0.32767]", + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.Extensions.Caching.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Caching.Memory": "(,10.0.32767]", + "Microsoft.Extensions.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Binder": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.CommandLine": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.FileExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Ini": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Json": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.KeyPerFile": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.UserSecrets": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Xml": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Features": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Composite": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Physical": "(,10.0.32767]", + "Microsoft.Extensions.FileSystemGlobbing": "(,10.0.32767]", + "Microsoft.Extensions.Hosting": "(,10.0.32767]", + "Microsoft.Extensions.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Http": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Core": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Stores": "(,10.0.32767]", + "Microsoft.Extensions.Localization": "(,10.0.32767]", + "Microsoft.Extensions.Localization.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Console": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Debug": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventLog": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventSource": "(,10.0.32767]", + "Microsoft.Extensions.Logging.TraceSource": "(,10.0.32767]", + "Microsoft.Extensions.ObjectPool": "(,10.0.32767]", + "Microsoft.Extensions.Options": "(,10.0.32767]", + "Microsoft.Extensions.Options.ConfigurationExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Options.DataAnnotations": "(,10.0.32767]", + "Microsoft.Extensions.Primitives": "(,10.0.32767]", + "Microsoft.Extensions.Validation": "(,10.0.32767]", + "Microsoft.Extensions.WebEncoders": "(,10.0.32767]", + "Microsoft.JSInterop": "(,10.0.32767]", + "Microsoft.Net.Http.Headers": "(,10.0.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.EventLog": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Cbor": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Cryptography.Xml": "(,10.0.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.RateLimiting": "(,10.0.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj", + "projectName": "Jellyfin.Model", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "MimeTypes": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[2.5.2, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.AspNetCore": "(,10.0.32767]", + "Microsoft.AspNetCore.Antiforgery": "(,10.0.32767]", + "Microsoft.AspNetCore.App": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.BearerToken": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Cookies": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.OAuth": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization.Policy": "(,10.0.32767]", + "Microsoft.AspNetCore.Components": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Endpoints": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Forms": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Server": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Web": "(,10.0.32767]", + "Microsoft.AspNetCore.Connections.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.CookiePolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.Internal": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.AspNetCore.HostFiltering": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Html.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Features": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Results": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpLogging": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpOverrides": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpsPolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Identity": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Metadata": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ApiExplorer": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.DataAnnotations": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Xml": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.RazorPages": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.TagHelpers": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ViewFeatures": "(,10.0.32767]", + "Microsoft.AspNetCore.OutputCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.RateLimiting": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor.Runtime": "(,10.0.32767]", + "Microsoft.AspNetCore.RequestDecompression": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCompression": "(,10.0.32767]", + "Microsoft.AspNetCore.Rewrite": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.HttpSys": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IIS": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IISIntegration": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets": "(,10.0.32767]", + "Microsoft.AspNetCore.Session": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticAssets": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticFiles": "(,10.0.32767]", + "Microsoft.AspNetCore.WebSockets": "(,10.0.32767]", + "Microsoft.AspNetCore.WebUtilities": "(,10.0.32767]", + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.Extensions.Caching.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Caching.Memory": "(,10.0.32767]", + "Microsoft.Extensions.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Binder": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.CommandLine": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.FileExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Ini": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Json": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.KeyPerFile": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.UserSecrets": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Xml": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Features": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Composite": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Physical": "(,10.0.32767]", + "Microsoft.Extensions.FileSystemGlobbing": "(,10.0.32767]", + "Microsoft.Extensions.Hosting": "(,10.0.32767]", + "Microsoft.Extensions.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Http": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Core": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Stores": "(,10.0.32767]", + "Microsoft.Extensions.Localization": "(,10.0.32767]", + "Microsoft.Extensions.Localization.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Console": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Debug": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventLog": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventSource": "(,10.0.32767]", + "Microsoft.Extensions.Logging.TraceSource": "(,10.0.32767]", + "Microsoft.Extensions.ObjectPool": "(,10.0.32767]", + "Microsoft.Extensions.Options": "(,10.0.32767]", + "Microsoft.Extensions.Options.ConfigurationExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Options.DataAnnotations": "(,10.0.32767]", + "Microsoft.Extensions.Primitives": "(,10.0.32767]", + "Microsoft.Extensions.Validation": "(,10.0.32767]", + "Microsoft.Extensions.WebEncoders": "(,10.0.32767]", + "Microsoft.JSInterop": "(,10.0.32767]", + "Microsoft.Net.Http.Headers": "(,10.0.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.EventLog": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Cbor": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Cryptography.Xml": "(,10.0.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.RateLimiting": "(,10.0.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "projectName": "Jellyfin.CodeAnalysis", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "netstandard2.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "netstandard2.0": { + "targetAlias": "netstandard2.0", + "projectReferences": {} + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "netstandard2.0": { + "targetAlias": "netstandard2.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[3.11.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.CSharp": { + "suppressParent": "All", + "target": "Package", + "version": "[5.0.0, )", + "versionCentrallyManaged": true + }, + "NETStandard.Library": { + "suppressParent": "All", + "target": "Package", + "version": "[2.0.3, )", + "autoReferenced": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/RuntimeIdentifierGraph.json" + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "version": "10.11.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "projectName": "Jellyfin.Database.Implementations", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Design": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Relational": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Tools": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Polly": { + "target": "Package", + "version": "[8.6.5, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "projectName": "Jellyfin.Extensions", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "noWarn": [ + "NU5104" + ], + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "Diacritics": { + "target": "Package", + "version": "[4.1.4, )", + "versionCentrallyManaged": true + }, + "ICU4N.Transliterator": { + "target": "Package", + "version": "[60.1.0-alpha.356, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + } + } +} \ No newline at end of file diff --git a/Emby.Naming/obj/Emby.Naming.csproj.nuget.g.props b/Emby.Naming/obj/Emby.Naming.csproj.nuget.g.props new file mode 100644 index 00000000..aa204e43 --- /dev/null +++ b/Emby.Naming/obj/Emby.Naming.csproj.nuget.g.props @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess> + <RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool> + <ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile> + <NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageRoot> + <NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageFolders> + <NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle> + <NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion> + </PropertyGroup> + <ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <SourceRoot Include="/home/wjones/.nuget/packages/" /> + </ItemGroup> + <ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props')" /> + <Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props')" /> + </ImportGroup> + <PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <PkgStyleCop_Analyzers_Unstable Condition=" '$(PkgStyleCop_Analyzers_Unstable)' == '' ">/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556</PkgStyleCop_Analyzers_Unstable> + <PkgSmartAnalyzers_MultithreadingAnalyzer Condition=" '$(PkgSmartAnalyzers_MultithreadingAnalyzer)' == '' ">/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31</PkgSmartAnalyzers_MultithreadingAnalyzer> + <PkgSerilogAnalyzer Condition=" '$(PkgSerilogAnalyzer)' == '' ">/home/wjones/.nuget/packages/seriloganalyzer/0.15.0</PkgSerilogAnalyzer> + <PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers Condition=" '$(PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers)' == '' ">/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0</PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers> + <PkgIDisposableAnalyzers Condition=" '$(PkgIDisposableAnalyzers)' == '' ">/home/wjones/.nuget/packages/idisposableanalyzers/4.0.8</PkgIDisposableAnalyzers> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/Emby.Naming/obj/Emby.Naming.csproj.nuget.g.targets b/Emby.Naming/obj/Emby.Naming.csproj.nuget.g.targets new file mode 100644 index 00000000..dda66fb2 --- /dev/null +++ b/Emby.Naming/obj/Emby.Naming.csproj.nuget.g.targets @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <Import Project="$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets')" /> + </ImportGroup> +</Project> \ No newline at end of file diff --git a/Emby.Naming/obj/project.assets.json b/Emby.Naming/obj/project.assets.json new file mode 100644 index 00000000..f6a18001 --- /dev/null +++ b/Emby.Naming/obj/project.assets.json @@ -0,0 +1,1642 @@ +{ + "version": 3, + "targets": { + "net10.0": { + "Diacritics/4.1.4": { + "type": "package", + "compile": { + "lib/net9.0/Diacritics.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Diacritics.dll": { + "related": ".xml" + } + } + }, + "ICU4N/60.1.0-alpha.356": { + "type": "package", + "dependencies": { + "J2N": "2.0.0", + "Microsoft.Extensions.Caching.Memory": "2.0.0" + }, + "compile": { + "lib/netstandard2.0/ICU4N.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/ICU4N.dll": { + "related": ".xml" + } + } + }, + "ICU4N.Transliterator/60.1.0-alpha.356": { + "type": "package", + "dependencies": { + "ICU4N": "60.1.0-alpha.356" + }, + "compile": { + "lib/netstandard2.0/ICU4N.Transliterator.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/ICU4N.Transliterator.dll": { + "related": ".xml" + } + } + }, + "IDisposableAnalyzers/4.0.8": { + "type": "package" + }, + "J2N/2.0.0": { + "type": "package", + "compile": { + "lib/net6.0/J2N.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/J2N.dll": { + "related": ".xml" + } + } + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers/4.14.0": { + "type": "package", + "build": { + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props": {}, + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets": {} + } + }, + "Microsoft.EntityFrameworkCore/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "10.0.3", + "Microsoft.EntityFrameworkCore.Analyzers": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props": {} + } + }, + "Microsoft.EntityFrameworkCore.Abstractions/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + } + }, + "Microsoft.EntityFrameworkCore.Analyzers/10.0.3": { + "type": "package" + }, + "Microsoft.EntityFrameworkCore.Relational/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + } + }, + "Microsoft.Extensions.Caching.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Caching.Memory/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Caching.Memory.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Caching.Memory.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Configuration.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.DependencyInjection/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Logging/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Logging.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Logging.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Logging.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets": {} + } + }, + "Microsoft.Extensions.Options/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Options.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Options.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/Microsoft.Extensions.Options.targets": {} + } + }, + "Microsoft.Extensions.Primitives/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.Extensions.Primitives.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Primitives.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Polly/8.6.5": { + "type": "package", + "dependencies": { + "Polly.Core": "8.6.5" + }, + "compile": { + "lib/net6.0/Polly.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net6.0/Polly.dll": { + "related": ".pdb;.xml" + } + } + }, + "Polly.Core/8.6.5": { + "type": "package", + "compile": { + "lib/net8.0/Polly.Core.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net8.0/Polly.Core.dll": { + "related": ".pdb;.xml" + } + } + }, + "SerilogAnalyzer/0.15.0": { + "type": "package" + }, + "SmartAnalyzers.MultithreadingAnalyzer/1.1.31": { + "type": "package" + }, + "StyleCop.Analyzers/1.2.0-beta.556": { + "type": "package", + "dependencies": { + "StyleCop.Analyzers.Unstable": "1.2.0.556" + } + }, + "StyleCop.Analyzers.Unstable/1.2.0.556": { + "type": "package" + }, + "Jellyfin.CodeAnalysis/1.0.0": { + "type": "project", + "framework": ".NETStandard,Version=v2.0", + "compile": { + "bin/placeholder/Jellyfin.CodeAnalysis.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.CodeAnalysis.dll": {} + } + }, + "Jellyfin.Common/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Common.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Common.dll": {} + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Jellyfin.Data/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Database.Implementations": "10.11.0", + "Microsoft.Extensions.Logging": "10.0.3" + }, + "compile": { + "bin/placeholder/Jellyfin.Data.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Data.dll": {} + } + }, + "Jellyfin.Database.Implementations/10.11.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Polly": "8.6.5" + }, + "compile": { + "bin/placeholder/Jellyfin.Database.Implementations.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Database.Implementations.dll": {} + } + }, + "Jellyfin.Extensions/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Diacritics": "4.1.4", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "Jellyfin.CodeAnalysis": "1.0.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Extensions.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Extensions.dll": {} + } + }, + "Jellyfin.Model/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Data": "10.12.0", + "Jellyfin.Extensions": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Model.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Model.dll": {} + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + } + } + }, + "libraries": { + "Diacritics/4.1.4": { + "sha512": "9JK2bI+oWnzOK4Lu+5FXt7czofD28AMnZ95np5fkSClqioPnga1JVpjaUxVJaEpc/5HIlfikc6YI33lm2qu4mw==", + "type": "package", + "path": "diacritics/4.1.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "README.md", + "diacritics.4.1.4.nupkg.sha512", + "diacritics.nuspec", + "lib/net48/Diacritics.dll", + "lib/net48/Diacritics.xml", + "lib/net7.0/Diacritics.dll", + "lib/net7.0/Diacritics.xml", + "lib/net8.0/Diacritics.dll", + "lib/net8.0/Diacritics.xml", + "lib/net9.0/Diacritics.dll", + "lib/net9.0/Diacritics.xml", + "lib/netstandard1.2/Diacritics.dll", + "lib/netstandard1.2/Diacritics.xml", + "lib/netstandard2.0/Diacritics.dll", + "lib/netstandard2.0/Diacritics.xml", + "lib/netstandard2.1/Diacritics.dll", + "lib/netstandard2.1/Diacritics.xml", + "logo.png" + ] + }, + "ICU4N/60.1.0-alpha.356": { + "sha512": "YMZtDnjcqWzziOKiE7w6Ma7Rl5vuFDxzOsUlHh1QyfghbNEIZQOLRs9MMfwCWAjX6n9UitrF6vLXy55Z5q+4Fg==", + "type": "package", + "path": "icu4n/60.1.0-alpha.356", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "icu4n.60.1.0-alpha.356.nupkg.sha512", + "icu4n.nuspec", + "lib/net40/ICU4N.dll", + "lib/net40/ICU4N.xml", + "lib/net451/ICU4N.dll", + "lib/net451/ICU4N.xml", + "lib/netstandard2.0/ICU4N.dll", + "lib/netstandard2.0/ICU4N.xml", + "unicode-icon-128x128.jpg" + ] + }, + "ICU4N.Transliterator/60.1.0-alpha.356": { + "sha512": "lFOSO6bbEtB6HkWMNDJAq+rFwVyi9g6xVc5O/2xHa6iZnV7wLVDqCbaQ4W4vIeBSQZAafqhxciaEkmAvSdzlCg==", + "type": "package", + "path": "icu4n.transliterator/60.1.0-alpha.356", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "icu4n.transliterator.60.1.0-alpha.356.nupkg.sha512", + "icu4n.transliterator.nuspec", + "lib/net40/ICU4N.Transliterator.dll", + "lib/net40/ICU4N.Transliterator.xml", + "lib/net451/ICU4N.Transliterator.dll", + "lib/net451/ICU4N.Transliterator.xml", + "lib/netstandard2.0/ICU4N.Transliterator.dll", + "lib/netstandard2.0/ICU4N.Transliterator.xml", + "unicode-icon-128x128.jpg" + ] + }, + "IDisposableAnalyzers/4.0.8": { + "sha512": "vNi4NMG0CcJyjXxiNDcQ21FwV/whM9o9OEZKD+oP7tuxAqFEzX/x5OhC3OZJqW/w+8GOtCmJPBquYgMWgz0rfQ==", + "type": "package", + "path": "idisposableanalyzers/4.0.8", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/IDisposableAnalyzers.dll", + "idisposableanalyzers.4.0.8.nupkg.sha512", + "idisposableanalyzers.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "J2N/2.0.0": { + "sha512": "M5bwDajAARZiyqupU+rHQJnsVLxNBOHJ8vKYHd8LcLIb1FgLfzzcJvc31Qo5Xz/GEHFjDF9ScjKL/ks/zRTXuA==", + "type": "package", + "path": "j2n/2.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "j2n-icon-100x100.png", + "j2n.2.0.0.nupkg.sha512", + "j2n.nuspec", + "lib/net40/J2N.dll", + "lib/net40/J2N.xml", + "lib/net45/J2N.dll", + "lib/net45/J2N.xml", + "lib/net5.0/J2N.dll", + "lib/net5.0/J2N.xml", + "lib/net6.0/J2N.dll", + "lib/net6.0/J2N.xml", + "lib/netcoreapp3.0/J2N.dll", + "lib/netcoreapp3.0/J2N.xml", + "lib/netstandard2.0/J2N.dll", + "lib/netstandard2.0/J2N.xml", + "lib/netstandard2.1/J2N.dll", + "lib/netstandard2.1/J2N.xml", + "readme.md" + ] + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers/4.14.0": { + "sha512": "gSWJlDWwmDhtbrEJGiHqvEjz9KthIiFD0qYB8zZ6a7z+xpMSPEtM9yTYELSa58iFWYlzSRqP9FXO6KoT3+ZMtg==", + "type": "package", + "path": "microsoft.codeanalysis.bannedapianalyzers/4.14.0", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.txt", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.dll", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.CSharp.BannedApiAnalyzers.dll", + "analyzers/dotnet/cs/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/de/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/es/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/fr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/it/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ja/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ko/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/pl/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/pt-BR/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ru/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/tr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hans/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hant/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.BannedApiAnalyzers.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.VisualBasic.BannedApiAnalyzers.dll", + "analyzers/dotnet/vb/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/de/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/es/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/fr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/it/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ja/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ko/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/pl/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/pt-BR/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ru/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/tr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hans/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hant/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props", + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets", + "documentation/Microsoft.CodeAnalysis.BannedApiAnalyzers.md", + "documentation/Microsoft.CodeAnalysis.BannedApiAnalyzers.sarif", + "editorconfig/AllRulesDefault/.editorconfig", + "editorconfig/AllRulesDisabled/.editorconfig", + "editorconfig/AllRulesEnabled/.editorconfig", + "editorconfig/ApiDesignRulesDefault/.editorconfig", + "editorconfig/ApiDesignRulesEnabled/.editorconfig", + "editorconfig/DataflowRulesDefault/.editorconfig", + "editorconfig/DataflowRulesEnabled/.editorconfig", + "editorconfig/PortedFromFxCopRulesDefault/.editorconfig", + "editorconfig/PortedFromFxCopRulesEnabled/.editorconfig", + "microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512", + "microsoft.codeanalysis.bannedapianalyzers.nuspec", + "rulesets/AllRulesDefault.ruleset", + "rulesets/AllRulesDisabled.ruleset", + "rulesets/AllRulesEnabled.ruleset", + "rulesets/ApiDesignRulesDefault.ruleset", + "rulesets/ApiDesignRulesEnabled.ruleset", + "rulesets/DataflowRulesDefault.ruleset", + "rulesets/DataflowRulesEnabled.ruleset", + "rulesets/PortedFromFxCopRulesDefault.ruleset", + "rulesets/PortedFromFxCopRulesEnabled.ruleset", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Microsoft.EntityFrameworkCore/10.0.3": { + "sha512": "CDEImwD4A7BseABJMCpLZnhfFjmPY/bHwhhS70elc6gLI/bYUEOhxWt7PmaNGYGhIEzOnStlCy5QcVb+8dod5Q==", + "type": "package", + "path": "microsoft.entityframeworkcore/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props", + "lib/net10.0/Microsoft.EntityFrameworkCore.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.xml", + "microsoft.entityframeworkcore.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Abstractions/10.0.3": { + "sha512": "c7Uoz381xnMHNBRB8eHRhGgzUtXbgddlbODhwZRrTSzZWDharp3RkJsFwhxyESbeXhCqmML7VdvjMQ7uu+HreA==", + "type": "package", + "path": "microsoft.entityframeworkcore.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.xml", + "microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.abstractions.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Analyzers/10.0.3": { + "sha512": "lVABgJTyTUNE7Bi0bSu4dWHiCHIXEGzTh/kLh0N07IgU/tIKwTeBPp8tgV4x2iKj4h7iPLo8oXzyHmLDGtAE1g==", + "type": "package", + "path": "microsoft.entityframeworkcore.analyzers/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "analyzers/dotnet/cs/Microsoft.EntityFrameworkCore.Analyzers.dll", + "docs/PACKAGE.md", + "microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.analyzers.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Relational/10.0.3": { + "sha512": "Pmh60OK9neVr/M0FJwm9hlzm2bD4Kd65SID8E6SP5c90tExNgXwORrlEWl0oGU/ig9ifpNN4PSpIrnHNozlT5w==", + "type": "package", + "path": "microsoft.entityframeworkcore.relational/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.xml", + "microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.relational.nuspec" + ] + }, + "Microsoft.Extensions.Caching.Abstractions/10.0.3": { + "sha512": "5dtXBvI8t3z8pF4tB38JYgi/enCL/DwSXxpqShgFz3SHJ7IzqFIMs6Gu5ik8sNZzcO9qQs3xIDpB3vDamkYG+Q==", + "type": "package", + "path": "microsoft.extensions.caching.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Caching.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Caching.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.xml", + "microsoft.extensions.caching.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.caching.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Caching.Memory/10.0.3": { + "sha512": "No4fVh0z30SWqiWFRoA4PNdrEco6OjXvCqRFvlmRgDQqqks2bRDdeavUgWEiAX153ZAwW9loUgbxcvuP4NKQLg==", + "type": "package", + "path": "microsoft.extensions.caching.memory/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Caching.Memory.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Caching.Memory.targets", + "lib/net10.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net10.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/net462/Microsoft.Extensions.Caching.Memory.dll", + "lib/net462/Microsoft.Extensions.Caching.Memory.xml", + "lib/net8.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net8.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/net9.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net9.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.xml", + "microsoft.extensions.caching.memory.10.0.3.nupkg.sha512", + "microsoft.extensions.caching.memory.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Configuration.Abstractions/10.0.3": { + "sha512": "xVDHL0+SIgemfh95fTO9cGLe17TWv/ZP0n7m01z8X6pzt2DmQpucioWR/mYZA1sRlkWnkXzfl0JweLNWmE9WMg==", + "type": "package", + "path": "microsoft.extensions.configuration.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Configuration.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Configuration.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "microsoft.extensions.configuration.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.configuration.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyInjection/10.0.3": { + "sha512": "2DLOmC0EkB2smVK8lPP1PIKEgL1arE3CMp9XSIQB/Y7ev5nnnyuM/PizKJ6QfLD08QCYoopSC9SFdbYglDomYg==", + "type": "package", + "path": "microsoft.extensions.dependencyinjection/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyInjection.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.targets", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/net462/Microsoft.Extensions.DependencyInjection.dll", + "lib/net462/Microsoft.Extensions.DependencyInjection.xml", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.dll", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.xml", + "microsoft.extensions.dependencyinjection.10.0.3.nupkg.sha512", + "microsoft.extensions.dependencyinjection.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/10.0.3": { + "sha512": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==", + "type": "package", + "path": "microsoft.extensions.dependencyinjection.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyInjection.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "microsoft.extensions.dependencyinjection.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.dependencyinjection.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Logging/10.0.3": { + "sha512": "8D9Er1cGXNjNDIB+VLBNHn386L5ls2FoiG9a6o12gyn+GG3w6jdfUhzT8dtBnKcevE7/fsVA8MS3FBgFfClFtQ==", + "type": "package", + "path": "microsoft.extensions.logging/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Logging.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Logging.targets", + "lib/net10.0/Microsoft.Extensions.Logging.dll", + "lib/net10.0/Microsoft.Extensions.Logging.xml", + "lib/net462/Microsoft.Extensions.Logging.dll", + "lib/net462/Microsoft.Extensions.Logging.xml", + "lib/net8.0/Microsoft.Extensions.Logging.dll", + "lib/net8.0/Microsoft.Extensions.Logging.xml", + "lib/net9.0/Microsoft.Extensions.Logging.dll", + "lib/net9.0/Microsoft.Extensions.Logging.xml", + "lib/netstandard2.0/Microsoft.Extensions.Logging.dll", + "lib/netstandard2.0/Microsoft.Extensions.Logging.xml", + "lib/netstandard2.1/Microsoft.Extensions.Logging.dll", + "lib/netstandard2.1/Microsoft.Extensions.Logging.xml", + "microsoft.extensions.logging.10.0.3.nupkg.sha512", + "microsoft.extensions.logging.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Logging.Abstractions/10.0.3": { + "sha512": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", + "type": "package", + "path": "microsoft.extensions.logging.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn3.11/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn3.11/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn4.0/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "buildTransitive/net461/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/net462/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.xml", + "microsoft.extensions.logging.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.logging.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Options/10.0.3": { + "sha512": "hU6WzGTPvPoLA2ng1ILvWQb3g0qORdlHNsxI8IcPLumJb3suimYUl+bbDzdo1V4KFsvVhnMWzysHpKbZaoDQPQ==", + "type": "package", + "path": "microsoft.extensions.options/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Options.SourceGeneration.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "buildTransitive/net461/Microsoft.Extensions.Options.targets", + "buildTransitive/net462/Microsoft.Extensions.Options.targets", + "buildTransitive/net8.0/Microsoft.Extensions.Options.targets", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Options.targets", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Options.targets", + "lib/net10.0/Microsoft.Extensions.Options.dll", + "lib/net10.0/Microsoft.Extensions.Options.xml", + "lib/net462/Microsoft.Extensions.Options.dll", + "lib/net462/Microsoft.Extensions.Options.xml", + "lib/net8.0/Microsoft.Extensions.Options.dll", + "lib/net8.0/Microsoft.Extensions.Options.xml", + "lib/net9.0/Microsoft.Extensions.Options.dll", + "lib/net9.0/Microsoft.Extensions.Options.xml", + "lib/netstandard2.0/Microsoft.Extensions.Options.dll", + "lib/netstandard2.0/Microsoft.Extensions.Options.xml", + "lib/netstandard2.1/Microsoft.Extensions.Options.dll", + "lib/netstandard2.1/Microsoft.Extensions.Options.xml", + "microsoft.extensions.options.10.0.3.nupkg.sha512", + "microsoft.extensions.options.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Primitives/10.0.3": { + "sha512": "GEcpTwo7sUoLGGNTqV1FZEuL+tTD9m81NX/mh099dqGNna07/UGZShKQNZRw4hv6nlliSUwYQgSYc7OR99Jufg==", + "type": "package", + "path": "microsoft.extensions.primitives/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Primitives.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Primitives.targets", + "lib/net10.0/Microsoft.Extensions.Primitives.dll", + "lib/net10.0/Microsoft.Extensions.Primitives.xml", + "lib/net462/Microsoft.Extensions.Primitives.dll", + "lib/net462/Microsoft.Extensions.Primitives.xml", + "lib/net8.0/Microsoft.Extensions.Primitives.dll", + "lib/net8.0/Microsoft.Extensions.Primitives.xml", + "lib/net9.0/Microsoft.Extensions.Primitives.dll", + "lib/net9.0/Microsoft.Extensions.Primitives.xml", + "lib/netstandard2.0/Microsoft.Extensions.Primitives.dll", + "lib/netstandard2.0/Microsoft.Extensions.Primitives.xml", + "microsoft.extensions.primitives.10.0.3.nupkg.sha512", + "microsoft.extensions.primitives.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Polly/8.6.5": { + "sha512": "VqtW2ZE/ALvQMAH1cQY3qZ2cF2OXa3oe/HKMdOv6Q02HCoEW0rsFNfcBONXlHBe1TnjWW1vdRxBEkPeq0/2FHA==", + "type": "package", + "path": "polly/8.6.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "lib/net462/Polly.dll", + "lib/net462/Polly.pdb", + "lib/net462/Polly.xml", + "lib/net472/Polly.dll", + "lib/net472/Polly.pdb", + "lib/net472/Polly.xml", + "lib/net6.0/Polly.dll", + "lib/net6.0/Polly.pdb", + "lib/net6.0/Polly.xml", + "lib/netstandard2.0/Polly.dll", + "lib/netstandard2.0/Polly.pdb", + "lib/netstandard2.0/Polly.xml", + "package-icon.png", + "package-readme.md", + "polly.8.6.5.nupkg.sha512", + "polly.nuspec" + ] + }, + "Polly.Core/8.6.5": { + "sha512": "t+sUVrIwvo7UmsgHGgOG9F0GDZSRIm47u2ylH17Gvcv1q5hNEwgD5GoBlFyc0kh/pebmPyrAgvGsR/65ZBaXlg==", + "type": "package", + "path": "polly.core/8.6.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "lib/net462/Polly.Core.dll", + "lib/net462/Polly.Core.pdb", + "lib/net462/Polly.Core.xml", + "lib/net472/Polly.Core.dll", + "lib/net472/Polly.Core.pdb", + "lib/net472/Polly.Core.xml", + "lib/net6.0/Polly.Core.dll", + "lib/net6.0/Polly.Core.pdb", + "lib/net6.0/Polly.Core.xml", + "lib/net8.0/Polly.Core.dll", + "lib/net8.0/Polly.Core.pdb", + "lib/net8.0/Polly.Core.xml", + "lib/netstandard2.0/Polly.Core.dll", + "lib/netstandard2.0/Polly.Core.pdb", + "lib/netstandard2.0/Polly.Core.xml", + "package-icon.png", + "package-readme.md", + "polly.core.8.6.5.nupkg.sha512", + "polly.core.nuspec" + ] + }, + "SerilogAnalyzer/0.15.0": { + "sha512": "sVpwfls4MfNnwIXLSGCgaUnV+c9kgJ8ia6GsyRcpd4Vs3gLogSDtSYBYrre2K2u/PNMo8GgG09RehwVnze70Tw==", + "type": "package", + "path": "seriloganalyzer/0.15.0", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/SerilogAnalyzer.dll", + "seriloganalyzer.0.15.0.nupkg.sha512", + "seriloganalyzer.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "SmartAnalyzers.MultithreadingAnalyzer/1.1.31": { + "sha512": "2f2k7bbhDd132ArglCKpzKoWBcp3uzbIFcb4aosnlqIKlfYKDE2HevBVRNVa+LkWFnjXFFWs47Bo96fu8iS//Q==", + "type": "package", + "path": "smartanalyzers.multithreadinganalyzer/1.1.31", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/SmartAnalyzers.MultithreadingAnalyzer.dll", + "smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512", + "smartanalyzers.multithreadinganalyzer.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "StyleCop.Analyzers/1.2.0-beta.556": { + "sha512": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", + "type": "package", + "path": "stylecop.analyzers/1.2.0-beta.556", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "THIRD-PARTY-NOTICES.txt", + "stylecop.analyzers.1.2.0-beta.556.nupkg.sha512", + "stylecop.analyzers.nuspec" + ] + }, + "StyleCop.Analyzers.Unstable/1.2.0.556": { + "sha512": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==", + "type": "package", + "path": "stylecop.analyzers.unstable/1.2.0.556", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "THIRD-PARTY-NOTICES.txt", + "analyzers/dotnet/cs/StyleCop.Analyzers.CodeFixes.dll", + "analyzers/dotnet/cs/StyleCop.Analyzers.dll", + "analyzers/dotnet/cs/de-DE/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/en-GB/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/es-MX/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/fr-FR/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/pl-PL/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/pt-BR/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/ru-RU/StyleCop.Analyzers.resources.dll", + "rulesets/StyleCopAnalyzersDefault.ruleset", + "stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512", + "stylecop.analyzers.unstable.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Jellyfin.CodeAnalysis/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "msbuildProject": "../src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "Jellyfin.Common/10.12.0": { + "type": "project", + "path": "../MediaBrowser.Common/MediaBrowser.Common.csproj", + "msbuildProject": "../MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "Jellyfin.Data/10.12.0": { + "type": "project", + "path": "../Jellyfin.Data/Jellyfin.Data.csproj", + "msbuildProject": "../Jellyfin.Data/Jellyfin.Data.csproj" + }, + "Jellyfin.Database.Implementations/10.11.0": { + "type": "project", + "path": "../src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "msbuildProject": "../src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + }, + "Jellyfin.Extensions/10.12.0": { + "type": "project", + "path": "../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "msbuildProject": "../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" + }, + "Jellyfin.Model/10.12.0": { + "type": "project", + "path": "../MediaBrowser.Model/MediaBrowser.Model.csproj", + "msbuildProject": "../MediaBrowser.Model/MediaBrowser.Model.csproj" + } + }, + "projectFileDependencyGroups": { + "net10.0": [ + "IDisposableAnalyzers >= 4.0.8", + "Jellyfin.CodeAnalysis >= 1.0.0", + "Jellyfin.Common >= 10.12.0", + "Jellyfin.Model >= 10.12.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers >= 4.14.0", + "SerilogAnalyzer >= 0.15.0", + "SmartAnalyzers.MultithreadingAnalyzer >= 1.1.31", + "StyleCop.Analyzers >= 1.2.0-beta.556" + ] + }, + "packageFolders": { + "/home/wjones/.nuget/packages/": {} + }, + "project": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj", + "projectName": "Jellyfin.Naming", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + } +} \ No newline at end of file diff --git a/Emby.Naming/obj/project.nuget.cache b/Emby.Naming/obj/project.nuget.cache new file mode 100644 index 00000000..19031a21 --- /dev/null +++ b/Emby.Naming/obj/project.nuget.cache @@ -0,0 +1,34 @@ +{ + "version": 2, + "dgSpecHash": "nPaubMzeXPg=", + "success": true, + "projectFilePath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj", + "expectedPackageFiles": [ + "/home/wjones/.nuget/packages/diacritics/4.1.4/diacritics.4.1.4.nupkg.sha512", + "/home/wjones/.nuget/packages/icu4n/60.1.0-alpha.356/icu4n.60.1.0-alpha.356.nupkg.sha512", + "/home/wjones/.nuget/packages/icu4n.transliterator/60.1.0-alpha.356/icu4n.transliterator.60.1.0-alpha.356.nupkg.sha512", + "/home/wjones/.nuget/packages/idisposableanalyzers/4.0.8/idisposableanalyzers.4.0.8.nupkg.sha512", + "/home/wjones/.nuget/packages/j2n/2.0.0/j2n.2.0.0.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0/microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore/10.0.3/microsoft.entityframeworkcore.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.abstractions/10.0.3/microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.analyzers/10.0.3/microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.relational/10.0.3/microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.caching.abstractions/10.0.3/microsoft.extensions.caching.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.caching.memory/10.0.3/microsoft.extensions.caching.memory.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.configuration.abstractions/10.0.3/microsoft.extensions.configuration.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection/10.0.3/microsoft.extensions.dependencyinjection.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/10.0.3/microsoft.extensions.dependencyinjection.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.logging/10.0.3/microsoft.extensions.logging.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.logging.abstractions/10.0.3/microsoft.extensions.logging.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.options/10.0.3/microsoft.extensions.options.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.primitives/10.0.3/microsoft.extensions.primitives.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/polly/8.6.5/polly.8.6.5.nupkg.sha512", + "/home/wjones/.nuget/packages/polly.core/8.6.5/polly.core.8.6.5.nupkg.sha512", + "/home/wjones/.nuget/packages/seriloganalyzer/0.15.0/seriloganalyzer.0.15.0.nupkg.sha512", + "/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31/smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512", + "/home/wjones/.nuget/packages/stylecop.analyzers/1.2.0-beta.556/stylecop.analyzers.1.2.0-beta.556.nupkg.sha512", + "/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556/stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Emby.Naming/obj/project.packagespec.json b/Emby.Naming/obj/project.packagespec.json new file mode 100644 index 00000000..23e63a5f --- /dev/null +++ b/Emby.Naming/obj/project.packagespec.json @@ -0,0 +1 @@ +"restore":{"projectUniqueName":"/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj","projectName":"Jellyfin.Naming","projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj","outputPath":"/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/obj/","projectStyle":"PackageReference","centralPackageVersionsManagementEnabled":true,"originalTargetFrameworks":["net10.0"],"sources":{"/usr/lib/dotnet/library-packs":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net10.0":{"targetAlias":"net10.0","projectReferences":{"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj"}}}},"warningProperties":{"allWarningsAsErrors":true,"warnAsError":["NU1605"],"warnNotAsError":["NU1902","NU1903"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"all"},"SdkAnalysisLevel":"10.0.100"}"frameworks":{"net10.0":{"targetAlias":"net10.0","dependencies":{"IDisposableAnalyzers":{"include":"Runtime, Build, Native, ContentFiles, Analyzers","suppressParent":"All","target":"Package","version":"[4.0.8, )","versionCentrallyManaged":true},"Microsoft.CodeAnalysis.BannedApiAnalyzers":{"include":"Runtime, Build, Native, ContentFiles, Analyzers","suppressParent":"All","target":"Package","version":"[4.14.0, )","versionCentrallyManaged":true},"SerilogAnalyzer":{"suppressParent":"All","target":"Package","version":"[0.15.0, )","versionCentrallyManaged":true},"SmartAnalyzers.MultithreadingAnalyzer":{"suppressParent":"All","target":"Package","version":"[1.1.31, )","versionCentrallyManaged":true},"StyleCop.Analyzers":{"suppressParent":"All","target":"Package","version":"[1.2.0-beta.556, )","versionCentrallyManaged":true}},"centralPackageVersions":{"AsyncKeyedLock":"8.0.2","AutoFixture":"4.18.1","AutoFixture.AutoMoq":"4.18.1","AutoFixture.Xunit2":"4.18.1","BDInfo":"0.8.0","BitFaster.Caching":"2.5.4","BlurHashSharp":"1.4.0-pre.1","BlurHashSharp.SkiaSharp":"1.4.0-pre.1","CommandLineParser":"2.9.1","coverlet.collector":"8.0.0","Diacritics":"4.1.4","DiscUtils.Udf":"0.16.13","DotNet.Glob":"3.1.3","FsCheck.Xunit":"3.3.2","HarfBuzzSharp.NativeAssets.Linux":"8.3.1.1","ICU4N.Transliterator":"60.1.0-alpha.356","IDisposableAnalyzers":"4.0.8","Ignore":"0.2.1","Jellyfin.XmlTv":"10.8.0","libse":"4.0.12","LrcParser":"2025.623.0","MetaBrainz.MusicBrainz":"8.0.1","Microsoft.AspNetCore.Authorization":"10.0.3","Microsoft.AspNetCore.Mvc.Testing":"10.0.3","Microsoft.CodeAnalysis.Analyzers":"3.11.0","Microsoft.CodeAnalysis.BannedApiAnalyzers":"4.14.0","Microsoft.CodeAnalysis.Common":"5.0.0","Microsoft.CodeAnalysis.CSharp":"5.0.0","Microsoft.Data.Sqlite":"10.0.3","Microsoft.EntityFrameworkCore.Design":"10.0.3","Microsoft.EntityFrameworkCore.Relational":"10.0.3","Microsoft.EntityFrameworkCore.Sqlite":"10.0.3","Microsoft.EntityFrameworkCore.Tools":"10.0.3","Microsoft.Extensions.Caching.Abstractions":"10.0.3","Microsoft.Extensions.Caching.Memory":"10.0.3","Microsoft.Extensions.Configuration.Abstractions":"10.0.3","Microsoft.Extensions.Configuration.Binder":"10.0.3","Microsoft.Extensions.DependencyInjection":"10.0.3","Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore":"10.0.3","Microsoft.Extensions.Hosting.Abstractions":"10.0.3","Microsoft.Extensions.Http":"10.0.3","Microsoft.Extensions.Logging":"10.0.3","Microsoft.Extensions.Options":"10.0.3","Microsoft.NET.Test.Sdk":"18.0.1","MimeTypes":"2.5.2","Moq":"4.18.4","Morestachio":"5.0.1.631","NEbml":"1.1.0.5","Newtonsoft.Json":"13.0.4","PlaylistsNET":"1.4.1","Polly":"8.6.5","prometheus-net":"8.2.1","prometheus-net.AspNetCore":"8.2.1","prometheus-net.DotNetRuntime":"4.4.1","Serilog.AspNetCore":"10.0.0","Serilog.Enrichers.Thread":"4.0.0","Serilog.Expressions":"5.0.0","Serilog.Settings.Configuration":"10.0.0","Serilog.Sinks.Async":"2.1.0","Serilog.Sinks.Console":"6.1.1","Serilog.Sinks.File":"7.0.0","Serilog.Sinks.Graylog":"3.1.1","SerilogAnalyzer":"0.15.0","SharpFuzz":"2.2.0","SkiaSharp":"[3.116.1]","SkiaSharp.HarfBuzz":"[3.116.1]","SkiaSharp.NativeAssets.Linux":"[3.116.1]","SmartAnalyzers.MultithreadingAnalyzer":"1.1.31","StyleCop.Analyzers":"1.2.0-beta.556","Svg.Skia":"3.4.1","Swashbuckle.AspNetCore":"7.3.2","Swashbuckle.AspNetCore.ReDoc":"6.9.0","System.Text.Json":"10.0.3","TagLibSharp":"2.3.0","TMDbLib":"2.3.0","UTF.Unknown":"2.6.0","xunit":"2.9.3","Xunit.Priority":"1.1.6","xunit.runner.visualstudio":"2.8.2","Xunit.SkippableFact":"1.5.61","z440.atl.core":"7.11.0"},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json","packagesToPrune":{"Microsoft.CSharp":"(,4.7.32767]","Microsoft.VisualBasic":"(,10.4.32767]","Microsoft.Win32.Primitives":"(,4.3.32767]","Microsoft.Win32.Registry":"(,5.0.32767]","runtime.any.System.Collections":"(,4.3.32767]","runtime.any.System.Diagnostics.Tools":"(,4.3.32767]","runtime.any.System.Diagnostics.Tracing":"(,4.3.32767]","runtime.any.System.Globalization":"(,4.3.32767]","runtime.any.System.Globalization.Calendars":"(,4.3.32767]","runtime.any.System.IO":"(,4.3.32767]","runtime.any.System.Reflection":"(,4.3.32767]","runtime.any.System.Reflection.Extensions":"(,4.3.32767]","runtime.any.System.Reflection.Primitives":"(,4.3.32767]","runtime.any.System.Resources.ResourceManager":"(,4.3.32767]","runtime.any.System.Runtime":"(,4.3.32767]","runtime.any.System.Runtime.Handles":"(,4.3.32767]","runtime.any.System.Runtime.InteropServices":"(,4.3.32767]","runtime.any.System.Text.Encoding":"(,4.3.32767]","runtime.any.System.Text.Encoding.Extensions":"(,4.3.32767]","runtime.any.System.Threading.Tasks":"(,4.3.32767]","runtime.any.System.Threading.Timer":"(,4.3.32767]","runtime.aot.System.Collections":"(,4.3.32767]","runtime.aot.System.Diagnostics.Tools":"(,4.3.32767]","runtime.aot.System.Diagnostics.Tracing":"(,4.3.32767]","runtime.aot.System.Globalization":"(,4.3.32767]","runtime.aot.System.Globalization.Calendars":"(,4.3.32767]","runtime.aot.System.IO":"(,4.3.32767]","runtime.aot.System.Reflection":"(,4.3.32767]","runtime.aot.System.Reflection.Extensions":"(,4.3.32767]","runtime.aot.System.Reflection.Primitives":"(,4.3.32767]","runtime.aot.System.Resources.ResourceManager":"(,4.3.32767]","runtime.aot.System.Runtime":"(,4.3.32767]","runtime.aot.System.Runtime.Handles":"(,4.3.32767]","runtime.aot.System.Runtime.InteropServices":"(,4.3.32767]","runtime.aot.System.Text.Encoding":"(,4.3.32767]","runtime.aot.System.Text.Encoding.Extensions":"(,4.3.32767]","runtime.aot.System.Threading.Tasks":"(,4.3.32767]","runtime.aot.System.Threading.Timer":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.unix.Microsoft.Win32.Primitives":"(,4.3.32767]","runtime.unix.System.Console":"(,4.3.32767]","runtime.unix.System.Diagnostics.Debug":"(,4.3.32767]","runtime.unix.System.IO.FileSystem":"(,4.3.32767]","runtime.unix.System.Net.Primitives":"(,4.3.32767]","runtime.unix.System.Net.Sockets":"(,4.3.32767]","runtime.unix.System.Private.Uri":"(,4.3.32767]","runtime.unix.System.Runtime.Extensions":"(,4.3.32767]","runtime.win.Microsoft.Win32.Primitives":"(,4.3.32767]","runtime.win.System.Console":"(,4.3.32767]","runtime.win.System.Diagnostics.Debug":"(,4.3.32767]","runtime.win.System.IO.FileSystem":"(,4.3.32767]","runtime.win.System.Net.Primitives":"(,4.3.32767]","runtime.win.System.Net.Sockets":"(,4.3.32767]","runtime.win.System.Runtime.Extensions":"(,4.3.32767]","runtime.win10-arm-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win10-arm64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win10-x64-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win10-x86-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win7-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win7-x86.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win7.System.Private.Uri":"(,4.3.32767]","runtime.win8-arm.runtime.native.System.IO.Compression":"(,4.3.32767]","System.AppContext":"(,4.3.32767]","System.Buffers":"(,5.0.32767]","System.Collections":"(,4.3.32767]","System.Collections.Concurrent":"(,4.3.32767]","System.Collections.Immutable":"(,10.0.32767]","System.Collections.NonGeneric":"(,4.3.32767]","System.Collections.Specialized":"(,4.3.32767]","System.ComponentModel":"(,4.3.32767]","System.ComponentModel.Annotations":"(,4.3.32767]","System.ComponentModel.EventBasedAsync":"(,4.3.32767]","System.ComponentModel.Primitives":"(,4.3.32767]","System.ComponentModel.TypeConverter":"(,4.3.32767]","System.Console":"(,4.3.32767]","System.Data.Common":"(,4.3.32767]","System.Data.DataSetExtensions":"(,4.4.32767]","System.Diagnostics.Contracts":"(,4.3.32767]","System.Diagnostics.Debug":"(,4.3.32767]","System.Diagnostics.DiagnosticSource":"(,10.0.32767]","System.Diagnostics.FileVersionInfo":"(,4.3.32767]","System.Diagnostics.Process":"(,4.3.32767]","System.Diagnostics.StackTrace":"(,4.3.32767]","System.Diagnostics.TextWriterTraceListener":"(,4.3.32767]","System.Diagnostics.Tools":"(,4.3.32767]","System.Diagnostics.TraceSource":"(,4.3.32767]","System.Diagnostics.Tracing":"(,4.3.32767]","System.Drawing.Primitives":"(,4.3.32767]","System.Dynamic.Runtime":"(,4.3.32767]","System.Formats.Asn1":"(,10.0.32767]","System.Formats.Tar":"(,10.0.32767]","System.Globalization":"(,4.3.32767]","System.Globalization.Calendars":"(,4.3.32767]","System.Globalization.Extensions":"(,4.3.32767]","System.IO":"(,4.3.32767]","System.IO.Compression":"(,4.3.32767]","System.IO.Compression.ZipFile":"(,4.3.32767]","System.IO.FileSystem":"(,4.3.32767]","System.IO.FileSystem.AccessControl":"(,4.4.32767]","System.IO.FileSystem.DriveInfo":"(,4.3.32767]","System.IO.FileSystem.Primitives":"(,4.3.32767]","System.IO.FileSystem.Watcher":"(,4.3.32767]","System.IO.IsolatedStorage":"(,4.3.32767]","System.IO.MemoryMappedFiles":"(,4.3.32767]","System.IO.Pipelines":"(,10.0.32767]","System.IO.Pipes":"(,4.3.32767]","System.IO.Pipes.AccessControl":"(,5.0.32767]","System.IO.UnmanagedMemoryStream":"(,4.3.32767]","System.Linq":"(,4.3.32767]","System.Linq.AsyncEnumerable":"(,10.0.32767]","System.Linq.Expressions":"(,4.3.32767]","System.Linq.Parallel":"(,4.3.32767]","System.Linq.Queryable":"(,4.3.32767]","System.Memory":"(,5.0.32767]","System.Net.Http":"(,4.3.32767]","System.Net.Http.Json":"(,10.0.32767]","System.Net.NameResolution":"(,4.3.32767]","System.Net.NetworkInformation":"(,4.3.32767]","System.Net.Ping":"(,4.3.32767]","System.Net.Primitives":"(,4.3.32767]","System.Net.Requests":"(,4.3.32767]","System.Net.Security":"(,4.3.32767]","System.Net.ServerSentEvents":"(,10.0.32767]","System.Net.Sockets":"(,4.3.32767]","System.Net.WebHeaderCollection":"(,4.3.32767]","System.Net.WebSockets":"(,4.3.32767]","System.Net.WebSockets.Client":"(,4.3.32767]","System.Numerics.Vectors":"(,5.0.32767]","System.ObjectModel":"(,4.3.32767]","System.Private.DataContractSerialization":"(,4.3.32767]","System.Private.Uri":"(,4.3.32767]","System.Reflection":"(,4.3.32767]","System.Reflection.DispatchProxy":"(,6.0.32767]","System.Reflection.Emit":"(,4.7.32767]","System.Reflection.Emit.ILGeneration":"(,4.7.32767]","System.Reflection.Emit.Lightweight":"(,4.7.32767]","System.Reflection.Extensions":"(,4.3.32767]","System.Reflection.Metadata":"(,10.0.32767]","System.Reflection.Primitives":"(,4.3.32767]","System.Reflection.TypeExtensions":"(,4.3.32767]","System.Resources.Reader":"(,4.3.32767]","System.Resources.ResourceManager":"(,4.3.32767]","System.Resources.Writer":"(,4.3.32767]","System.Runtime":"(,4.3.32767]","System.Runtime.CompilerServices.Unsafe":"(,7.0.32767]","System.Runtime.CompilerServices.VisualC":"(,4.3.32767]","System.Runtime.Extensions":"(,4.3.32767]","System.Runtime.Handles":"(,4.3.32767]","System.Runtime.InteropServices":"(,4.3.32767]","System.Runtime.InteropServices.RuntimeInformation":"(,4.3.32767]","System.Runtime.Loader":"(,4.3.32767]","System.Runtime.Numerics":"(,4.3.32767]","System.Runtime.Serialization.Formatters":"(,4.3.32767]","System.Runtime.Serialization.Json":"(,4.3.32767]","System.Runtime.Serialization.Primitives":"(,4.3.32767]","System.Runtime.Serialization.Xml":"(,4.3.32767]","System.Security.AccessControl":"(,6.0.32767]","System.Security.Claims":"(,4.3.32767]","System.Security.Cryptography.Algorithms":"(,4.3.32767]","System.Security.Cryptography.Cng":"(,5.0.32767]","System.Security.Cryptography.Csp":"(,4.3.32767]","System.Security.Cryptography.Encoding":"(,4.3.32767]","System.Security.Cryptography.OpenSsl":"(,5.0.32767]","System.Security.Cryptography.Primitives":"(,4.3.32767]","System.Security.Cryptography.X509Certificates":"(,4.3.32767]","System.Security.Principal":"(,4.3.32767]","System.Security.Principal.Windows":"(,5.0.32767]","System.Security.SecureString":"(,4.3.32767]","System.Text.Encoding":"(,4.3.32767]","System.Text.Encoding.CodePages":"(,10.0.32767]","System.Text.Encoding.Extensions":"(,4.3.32767]","System.Text.Encodings.Web":"(,10.0.32767]","System.Text.Json":"(,10.0.32767]","System.Text.RegularExpressions":"(,4.3.32767]","System.Threading":"(,4.3.32767]","System.Threading.AccessControl":"(,10.0.32767]","System.Threading.Channels":"(,10.0.32767]","System.Threading.Overlapped":"(,4.3.32767]","System.Threading.Tasks":"(,4.3.32767]","System.Threading.Tasks.Dataflow":"(,10.0.32767]","System.Threading.Tasks.Extensions":"(,5.0.32767]","System.Threading.Tasks.Parallel":"(,4.3.32767]","System.Threading.Thread":"(,4.3.32767]","System.Threading.ThreadPool":"(,4.3.32767]","System.Threading.Timer":"(,4.3.32767]","System.ValueTuple":"(,4.5.32767]","System.Xml.ReaderWriter":"(,4.3.32767]","System.Xml.XDocument":"(,4.3.32767]","System.Xml.XmlDocument":"(,4.3.32767]","System.Xml.XmlSerializer":"(,4.3.32767]","System.Xml.XPath":"(,4.3.32767]","System.Xml.XPath.XDocument":"(,5.0.32767]"}}} \ No newline at end of file diff --git a/Emby.Naming/obj/rider.project.model.nuget.info b/Emby.Naming/obj/rider.project.model.nuget.info new file mode 100644 index 00000000..74f1bac0 --- /dev/null +++ b/Emby.Naming/obj/rider.project.model.nuget.info @@ -0,0 +1 @@ +17714532054800000 \ No newline at end of file diff --git a/Emby.Naming/obj/rider.project.restore.info b/Emby.Naming/obj/rider.project.restore.info new file mode 100644 index 00000000..f97b9d1a --- /dev/null +++ b/Emby.Naming/obj/rider.project.restore.info @@ -0,0 +1 @@ +17715044167700000 \ No newline at end of file diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj new file mode 100644 index 00000000..3faeae38 --- /dev/null +++ b/Emby.Photos/Emby.Photos.csproj @@ -0,0 +1,42 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <!-- ProjectGuid is only included as a requirement for SonarQube analysis --> + <PropertyGroup> + <ProjectGuid>{89AB4548-770D-41FD-A891-8DAFF44F452C}</ProjectGuid> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> + <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> + </ItemGroup> + + <ItemGroup> + <Compile Include="..\SharedVersion.cs" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="TagLibSharp" /> + </ItemGroup> + + <PropertyGroup> + <TargetFramework>net10.0</TargetFramework> + <GenerateAssemblyInfo>false</GenerateAssemblyInfo> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + </PropertyGroup> + + <!-- Code Analyzers --> + <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="IDisposableAnalyzers"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> + <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> + </ItemGroup> + +</Project> diff --git a/Emby.Photos/PhotoProvider.cs b/Emby.Photos/PhotoProvider.cs new file mode 100644 index 00000000..e24c067d --- /dev/null +++ b/Emby.Photos/PhotoProvider.cs @@ -0,0 +1,175 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Entities; +using Microsoft.Extensions.Logging; +using TagLib; +using TagLib.IFD; +using TagLib.IFD.Entries; +using TagLib.IFD.Tags; + +namespace Emby.Photos; + +/// <summary> +/// Metadata provider for photos. +/// </summary> +public class PhotoProvider : ICustomMetadataProvider<Photo>, IForcedProvider, IHasItemChangeMonitor +{ + private readonly ILogger<PhotoProvider> _logger; + private readonly IImageProcessor _imageProcessor; + + // Other extensions might cause taglib to hang + private readonly string[] _includeExtensions = [".jpg", ".jpeg", ".png", ".tiff", ".cr2", ".webp", ".avif"]; + + /// <summary> + /// Initializes a new instance of the <see cref="PhotoProvider" /> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="imageProcessor">The image processor.</param> + public PhotoProvider(ILogger<PhotoProvider> logger, IImageProcessor imageProcessor) + { + _logger = logger; + _imageProcessor = imageProcessor; + } + + /// <inheritdoc /> + public string Name => "Embedded Information"; + + /// <inheritdoc /> + public bool HasChanged(BaseItem item, IDirectoryService directoryService) + { + if (item.IsFileProtocol) + { + var file = directoryService.GetFile(item.Path); + return file is not null && item.HasChanged(file.LastWriteTimeUtc); + } + + return false; + } + + /// <inheritdoc /> + public Task<ItemUpdateType> FetchAsync(Photo item, MetadataRefreshOptions options, CancellationToken cancellationToken) + { + item.SetImagePath(ImageType.Primary, item.Path); + + // Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs + if (_includeExtensions.Contains(Path.GetExtension(item.Path.AsSpan()), StringComparison.OrdinalIgnoreCase)) + { + try + { + using var file = TagLib.File.Create(item.Path); + if (file.GetTag(TagTypes.TiffIFD) is IFDTag tag) + { + var structure = tag.Structure; + if (structure?.GetEntry(0, (ushort)IFDEntryTag.ExifIFD) is SubIFDEntry exif) + { + var exifStructure = exif.Structure; + if (exifStructure is not null) + { + if (exifStructure.GetEntry(0, (ushort)ExifEntryTag.ApertureValue) is RationalIFDEntry apertureEntry) + { + item.Aperture = (double)apertureEntry.Value.Numerator / apertureEntry.Value.Denominator; + } + + if (exifStructure.GetEntry(0, (ushort)ExifEntryTag.ShutterSpeedValue) is RationalIFDEntry shutterSpeedEntry) + { + item.ShutterSpeed = (double)shutterSpeedEntry.Value.Numerator / shutterSpeedEntry.Value.Denominator; + } + } + } + } + + if (file is TagLib.Image.File image) + { + item.CameraMake = image.ImageTag.Make; + item.CameraModel = image.ImageTag.Model; + + item.Width = image.Properties.PhotoWidth; + item.Height = image.Properties.PhotoHeight; + + item.CommunityRating = image.ImageTag.Rating; + + item.Overview = image.ImageTag.Comment; + + if (!string.IsNullOrWhiteSpace(image.ImageTag.Title) + && !item.LockedFields.Contains(MetadataField.Name)) + { + item.Name = image.ImageTag.Title; + } + + var dateTaken = image.ImageTag.DateTime; + if (dateTaken.HasValue) + { + item.DateCreated = dateTaken.Value.ToUniversalTime(); + item.PremiereDate = dateTaken.Value; + item.ProductionYear = dateTaken.Value.Year; + } + + item.Genres = image.ImageTag.Genres; + item.Tags = image.ImageTag.Keywords; + item.Software = image.ImageTag.Software; + + if (image.ImageTag.Orientation == TagLib.Image.ImageOrientation.None) + { + item.Orientation = null; + } + else if (Enum.TryParse(image.ImageTag.Orientation.ToString(), true, out ImageOrientation orientation)) + { + item.Orientation = orientation; + } + + item.ExposureTime = image.ImageTag.ExposureTime; + item.FocalLength = image.ImageTag.FocalLength; + + item.Latitude = image.ImageTag.Latitude; + item.Longitude = image.ImageTag.Longitude; + item.Altitude = image.ImageTag.Altitude; + + if (image.ImageTag.ISOSpeedRatings.HasValue) + { + item.IsoSpeedRating = Convert.ToInt32(image.ImageTag.ISOSpeedRatings.Value); + } + else + { + item.IsoSpeedRating = null; + } + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Image Provider - Error reading image tag for {0}", item.Path); + } + } + + if (item.Width <= 0 || item.Height <= 0) + { + var img = item.GetImageInfo(ImageType.Primary, 0); + + try + { + var size = _imageProcessor.GetImageDimensions(item, img); + + if (size.Width > 0 && size.Height > 0) + { + item.Width = size.Width; + item.Height = size.Height; + } + } + catch (ArgumentException) + { + // format not supported + } + } + + const ItemUpdateType Result = ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataImport; + return Task.FromResult(Result); + } +} diff --git a/Emby.Photos/Properties/AssemblyInfo.cs b/Emby.Photos/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..a3bb4362 --- /dev/null +++ b/Emby.Photos/Properties/AssemblyInfo.cs @@ -0,0 +1,21 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Emby.Photos")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Jellyfin Project")] +[assembly: AssemblyProduct("Jellyfin Server")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] diff --git a/Emby.Photos/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs b/Emby.Photos/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs new file mode 100644 index 00000000..925b1351 --- /dev/null +++ b/Emby.Photos/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// <autogenerated /> +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")] diff --git a/Emby.Photos/obj/Debug/net10.0/Emby.Photos.GeneratedMSBuildEditorConfig.editorconfig b/Emby.Photos/obj/Debug/net10.0/Emby.Photos.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 00000000..e33fe548 --- /dev/null +++ b/Emby.Photos/obj/Debug/net10.0/Emby.Photos.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,27 @@ +is_global = true +build_property.TargetFramework = net10.0 +build_property.TargetFramework = net10.0 +build_property.TargetFrameworkIdentifier = .NETCoreApp +build_property.TargetFrameworkIdentifier = .NETCoreApp +build_property.TargetFrameworkVersion = v10.0 +build_property.TargetFrameworkVersion = v10.0 +build_property.TargetPlatformMinVersion = +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = Emby.Photos +build_property.ProjectDir = /srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 10.0 +build_property.EnableCodeStyleSeverity = diff --git a/Emby.Photos/obj/Debug/net10.0/Emby.Photos.assets.cache b/Emby.Photos/obj/Debug/net10.0/Emby.Photos.assets.cache new file mode 100644 index 0000000000000000000000000000000000000000..f62aa6b1ccb2a871ff53d33d05268f37c89f4ff0 GIT binary patch literal 17212 zcmd5@TXWmi5mp*Yikw=GuXPf;k(AoG*`V&0(mILmr7g*dC8w9vZ9(uzf(!yI09t1I z-05`EX*$zs^44~yZ~al7JomBBooT<tg=YaCf)Gs0;>;NmhkJIvv*)(EXR&o}ZQ<K} zeSKeEeK7EsbAylm`-e;ad^mCWn>+vds{Gf--+cYY^8arC=h4_d{yo#z_bTYBrG2L+ zN{0uIExeLuH}-^Is_VvMeGkuO)%5C)r&lbY+q%{KwQ#-Cv^K8ImE?2P@oh9Ryi$3& zMZOS|YgNno76j4fB^3JoGRl`PWlFA@hU<9FuCEzRH5Bfdo>tLqTU7PBDaB7~6F{WI zv(aL*1SSC_VzL-8S*V*zkf$P${rLXs#erPf-q+pw5wL!d7pt&+({Jv&dQBWU?qkDo zg(lTGF^=J#>Y~n-fW>OeVu@HNl{pY8a}eKu&kgXYFKo|rY!8EP@0xoJSNHL#RZP1o z+)_S>lF3qn$zs4n+jbhRA=ZQ~BIazKzQRy{T>!P^?CqKMo~Bnk-_;Gm?sY_sX>K*` zX>FQTkesnA690O{E&}Vg9$!>L4D6o@fOYE6itm2NAycKN8OqNySH34)6AFxHWNBQR znCY;-w?oo+=5eji-bPyu?S;R~){0ltT|XA*X|;xh^|9{?y^1l6>q`^bgf=yyTo>EK zAO_)S8wmz+JqLFt0ym8B!)#^vO-mR~T`o|qVS7Gp$UroSr8+Y+s|H0am(Vf>;Ayl8 zu9{CpV)~qou)K!v+VNpwtBoT7h`#TjJj_H=G5uZL^M$J!y0MQ@DotuLSU{FpDd8xZ z#dKLT{ne0UDI_8N)44;Rb0{=9&ZAsFxrnlsDY0tmhHLsJmMG2}DQX^~mRtXE8JED5 zWL!oe8Puz-Oc|!pn6^u^nD5&7E#0c`>)I3sH|i7y-LC3x6=Pm1FFl;z<if6iISG3m zg@nC<Vs{s&VRy1U%Y-I%-*Jy1dneJ5LwOUTIh3n-p3j7GFll4_aO}igrSZEvndsoP zi0gOpO&xp-#mVF<4G6_~7jMRqZs|bbTd(4DW3y)jvZ;r3rV*A=l;3A>F_~bL$3%W) z3+#~L12G0z9Om12{_V5CbcGch!@6~J^*v~{<q*G@f_NRzT{df`NqIm<yTDNtDI3T8 zLnl4nNP&C@&%en#^`w{>V@=c?x7kI5vRRC7BWT8z_ihT;dw6aZz{QQD{IGZ{j_Ums zst@q|=iIYS_B(0#sSK$OD@3wLnth#y_ht(4Ej$;Rg9(i!E!87JjVx5Q<#2<Pwj2#5 z|5}zRsfxOQ32hrq(>@S^VVZl$ms@Siaa%s{rIa4W@%*;}1FwKsY+bAHcA5keDe#kc z&NhaW`%q-}=T>QK*t3zIN+FrXbCKy7K`ExX7*{8CahHRpBVq|f)Rpo)x~`Jg&tU4} zZUrHkMe&Zl3D#Y+2J7w#LLQNG*%i!Is|jBPw;`V&x^sA5&%|SkN(~1p8GYJJYyoj& zZ*v6%u-)NevyRV4f<DAIb^QA%cQOUl9m{H#=CoNUh77GOs^*-35OKZ}asCM3*~W3e z8Lql3lI-VN?9=E!PRaNL&!1+Z_w_vtiAOOJ7>iTj9E}iH)sDWmZdSs;3Dea=ByADj zq^l(q`j9*D3_7b<D>T&f;X3Pyt{88ws~+o!N#Q!89#N?7%0?@Gw&2w_g&)Rw>UAQZ z4_V{q+B_}#=d^T+=&?WWSm@Wq(057t58)q*;356-GF$=reovz563rv(Ym!<KtRjl{ zGbuE__YwI!UC85jC(*u}O)HIIB4+(!F`wA(OT&1Zv6kn0OdVQ@#C1$f$Bh&s<6aMA z$I03>(E)IxO=!Z91g;AyL2)<}@r)9+g18OTRb|WQAS2dl)T)z?sMYME1BeLTQp%Hu z0@3sbTX*m)*k$S0>2SZLl|+wcQz0y>yPhkaM3m>Z+k!k){bqZ$_ArVX(f_ZqeUhFj zNc)B=b{~fNDG<JlLR-QL3T>OKC~GJ`L@A@(MY)GEF4fXyky0E5ar>A#Mcs)xJ*$|n zqyBPASy5;(W<eo8VnHE46^jkj-^``kDA*S>rf?lGrf{8#F>Ozy?K%$wbhj$P*C|RD zbGsjL+v>pWQ`DbhBNQHYackPA2@9QNDl89BPXl!=^E3rP5SV%nVYa8W)^!gLvt^%( zIm2s=HxIA%Hmt^k@UYr(hSL}X9!@J5Mq}J~80}cYXAHv#=3Z<fpOml}gEz+D<#(|a zS86>9moaEOTvo&<G@?kj&LW1n{hipv;0B4tz};Yadtu=*hKGm8UQk$!q2Xb%lNSzS zKzTT<&>TycfxQTWF>pK#mVa$Zt5XqM%02&8Mks^E!(S&m?8QLyu-7q$yBLBgwrdob zZoLn4F>pN0RX{yO!&?ji4{sfNSc~D{VXfYUvzP!L&U%KB4CC1IkPPF^Lo%4a&+*23 zfY{^1O=${Y(T(*s$_~myl+RFpgz{sQo|xJQpT)AdsZFx!CR-)@C#dhODb7G}Q=CB1 z4Yvx!Pf>rIraFVjO?84uH{U9VpQFB@DbJ*FQ=X*JjkrqM&rpA?rar?s0@<;tPZ&p0 zR2Uzj{-n493_f=S2tM7QtKk0}_4&<_=jSRg=-gEx=+`?y{{`xcoV&euB^Y?_N)ULu zaaZa6m#BYXt_H))T@AuZH}NXGI_h7LE5gunSA@{g4ZR9&1@$M-Rbd_Bt_pR8Zthhb zF;HJ<j`!S^Vc@wdL*VH~Uj@F3`uuy#^K*3=bnfa9bh_zRK^Le$*{%@l6nBNFQ<MRq z>eMdk3-|(&pOV|#jPE5F!YN4Rhbd(Ss1WX<zQ}a%y(`7Qb61MgOBn+y@cXDQpx&ac z7DLEgEka0{1S*6k>W|$OV>r1hMmQ<MK!x)F^}Tb|7y$075dg|OPyu+1`e$(Em_+W% zkwnT!P)W2<pY8nQ&s2z=AMWar>`yw#uA#oixARN`bn8QFe-VXHETO10V56QsgnmNP zN*e+<D~qmkh@^U?kko39t*X-uukJbrB!NB-3jL;g{_Eg!S0F`X*R-`|-Pe;(sg}QD zEe*8vTq8+e(LK>#qm^rQOVp648zh)|-6>LX-AJy7@0qk*d8;n@%DNOMpY`B=np2?8 z729y=%>eD52w$RMa61sBWHcDsQOH=SRhrtSUZX^I_B++;2}6dTeTOK+n~gQ$Z-uXG zm8%z3m$(-=&1ZOl3vZ+J2k&sPEAcLuwFBX0ZQdM+fiQ`R@n`x@re%hS0&2?m5ad@5 znHTfv+w_6(-I073>I2~$9$A;?L#<aX+OuBzgI5sFZ0Y`f*^WkJB+60h_(B3plfjVS zy@J=H7VYm9gef$yrC$%Ysz`%sy&j-Uw)voAi)2zxkQ0X78*~im@#k4rqTKxK6q{R7 zgM7L5M@Yu`Fh65Hai}{y7e$fdFY|Kd^2?BoNP>DItM1O`JBW}bys8z+$ddvzm7BXv zEYFcLG})bP!f689qh=U=d_)oHG0vga1Vou^&1Z)k1;xEjr`eI&h(@khdecf*jVLfC z5~N7BdgF|?*qdEN#CtI~b=PoAoF1F*VpxiAw+yQ|0W`hA$Wf5lSfqe38*4Div>lCl z%+p7cnYR5wrdEHDFnckgP9)5-RIYg2ZqIn-xmMUok+?dhkj&D32<j;q17a{bt1py2 zGLKBd^sVNyfE^~P{j0j=iB}#7&#|6}>Zu*KAx_cJb2~Hbr}OLF8=38DtvpbeJ~>{- zp339~TU7CaXF0pg5#G^MWW@~!2X`wK>!WO0ZSP#BGP$(O=6*U;d1%4&gum0Qi$S-6 zW2YvD@Lpzf-7$3Ql#h`+zbri8v;*6ByHTNWywsLeOY>q_X4TT;Q0t(-qROfzlELV# z9+i1{igZ|_)UqksmU1?E@>IOVh~@I^biQ#bvz=O=S2xsL6T3S)HkE@c-MbEe6RhRI zaZ@QlwJ8J6d~V`2s1aq$c&`O(HR_lkA3L@ItL=>BraGM5Gl#<LR$v*Hd9F0O&Zc&_ yvn`)8^hyV@i%-DNJkzQtUvo6^M80F%Qw-YS)rku&O6h5gwCXyzWfDZ_eElDHwSvC@ literal 0 HcmV?d00001 diff --git a/Emby.Photos/obj/Debug/net10.0/Emby.Photos.csproj.AssemblyReference.cache b/Emby.Photos/obj/Debug/net10.0/Emby.Photos.csproj.AssemblyReference.cache new file mode 100644 index 0000000000000000000000000000000000000000..1717eb5655e255fb350850a709d1e9100b9cf087 GIT binary patch literal 4878 zcmds5X;c$e6y|{_pare8h(H@fIlz(09+D`vfPf1|L9})OotaD^FibEBT57=kR1i`@ z6niRG3$4YtU{$oDh~U1IN?GcHr=YmhmM$t{Piy;Tk}%=4Ct~%NFDH4K_ukE$?|$FC zxo-x7Ar1}>U^X{JlTL6KrD`Zb$3>{jWJ1r)K=Cv*8D3**y$aRo2`z%7c#4`z<_Zuo zBI1&2j7t%E3Buz>sP)n2C6VSOh>|1$fWPe!>KnePs?7xeFzjEefFoSs4BrFjm9Q}u zbiV|C3|MLz09zbj8)F_RhF=HLcJMlWRyd!w1@QG??iBvJj?H7627#{5@9&mvPK{eQ z>Gtl4TjRoiJm73xShLh9Y3M4*8RYZMj?g1XQ75w&%nw-l@Mv9n{^r9KiQkP^O!O+R zTLS`&H}?$PnKNpj>7&1V7hm_}7sVSoyJfPZXp;*u;`Xu(lDczu!><AIGDTc`!#h89 zztx04Zotk07kE!ZecExOLh1w({)KfMXAYs&sWns><2{5ojuV+l>NB-O7)502wJ14` zGcyw-)%Y|bD?yV+P+<}=E|g%nNJ@wZp_C_t8!!#T*yT&9MsclLuf}y;5yEGQl0l*- z_0F*jeUUA{AAgNz$c>!_(5(y|bZ^`GQe$QegtLY$PG<A|;G{B1(z*qhX&0Ls&lFKy z2@m4pg`i|c3W^BD5~~z-dX!S4TBQIYof0`)q+mg274lZMUZO!dGc#a<bw^A84AQ$8 z2<CsX*AQ`L``E}DPC42Xl<)vxD~dk%Y1SrGZae0eGcR%3?5cCRalQ`kr=Aa7?pRsu z;s3$ZlH$D3ss*9L(Gah*^&XE74}ITXv@PTt|A12|DM^M7(=2g|@93MOr!MYl&f9z8 zkpI4doa&nQiVv@yOBr{GjlPA6E?mhYw1bh}FMABe8SX(oU<!ker2#sNc|BQggbL$_ z;!tfW9wCtNF{_6F^<+*Es9;J*s$rTy^&0I{P?XTekY((<djbn%o@OrlE_`tbocmvJ zpDUj-yL_e!D45})W~^aFXZWcCn_%*AO)EDClM@_gvba?%bpH&YRO;PY6qR2=Rh-G2 zoI5_n$?@f%3p_V$ysYeON<S}asTB!M=ZAz0_+hQPcQcThOtreMKgq_|KK(s#3@G2{ zdGpNaiq`JQjjN7ry<c``&d7Po9B*@S+k?CpZQU_T_o%saw9B+|$@#D)WQ^CSgyTnV zVSiOV{;6*4saRLu6-9gF7ABPL700xcRH%1@Xo!w=JmZu8<TI5}CK81zC9+UdCXt~k zt8akDuHVzuxK^XnsPqUy>DBryl@?7W7HPC;xJFBG`E(Eqt(4GL-08grQH(*f)gTfY zuz^&^;Y@gs$?tx4Er6TlMx8AEGw^s%9yDM^MeCbq_KuRT>oly~r#-D0oaa9BhB3LK zr~6vzyd`1rJN$3tofrXrySm=3X02b@^x)0ow_oub>2)gS-U*^DsndB{VFO-0A@$%y ztx3N7PMAl1ooLO%8M`a@)at@IJ>TACik|DV@@T34c*ip3feRfmJ%xt`<@%O>7~ELR z@VN8qENM{NuK579i<@QG?Bo7Wj4Cl%s7xpo$dpQyknnmb&ISLUV+6%?Fpc7RSS9G5 zp73xRYnJ`jC%RkM54P(C#Mb=9l>*g&Cp=eOta=t*XbBRfMS+q}uZs9yTUHan+k-5c z2(<4G*7(I$1leB~JXbx$+D#J_fu%#P=_(fyTXVi`b*qSi3y{8f{tYK$+a2bl<;G4k zhcIg*r;l3J_TP0pYiNVkd>c=$AN$ntVRiv#Xh<@PD@7zMJi5x5Ne{C+P5iO2Aei~* za%N_jtZoHB_f}wi!GJCF+!*`k#aCRsuTPm#5+Ii!xoAup5>u%l-LFmh;)LJK{ZUO$ z-`q7+3oaH^A8zwo{blxI9VhMOw02SD{7sVW6ZZNw-pG*&)(s7GJ=nU$o6BE~=0)6$ zJEl15-1Fc-YK2eas}Wy`N`78mm568gUM};CSpHedIaB@PK~Ysz>LK9<Q}<TUCI-vt zvcKzldd8jylNo%h&!zpX*>Nc<RH<;GN~)3y#5hhPr`r?Gh>ZUcrm*Nk0^HX^LSJpM zOt_gjqNiV6I;OKApC>1Ac4I=1Cc{T6x)fBK0qbueWWw@<#WwI0(BxP(Hj7@w#z~Q( zt!BYx`fxkgW}d0bt+gDZ2}T)E8&CIiWf(MTH!N6`mJD=P&bIh_`K3r#Egg<%u1<cC z@$T%0S+c*Ua#Q!O%C18i4v5bVE6YB4QQ?z6=aAd|q7_MB{TiG9t@v_t`%CpZw`A9^ z*9=!Q{4vt2)~Bnr<F`*^E{#C1Z65sR8*<5!SLByUj&D5Y>gswmq=?gKsP*_b_SP>C u5AqEQw=q(?g552T&fQ-L6gDVXei)zir+8Ch5&?msB9TNY!^Bu`@%9fJ_!r0k literal 0 HcmV?d00001 diff --git a/Emby.Photos/obj/Emby.Photos.csproj.nuget.dgspec.json b/Emby.Photos/obj/Emby.Photos.csproj.nuget.dgspec.json new file mode 100644 index 00000000..b7f79971 --- /dev/null +++ b/Emby.Photos/obj/Emby.Photos.csproj.nuget.dgspec.json @@ -0,0 +1,4754 @@ +{ + "format": 1, + "restore": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj": {} + }, + "projects": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj", + "projectName": "Jellyfin.Naming", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj", + "projectName": "Emby.Photos", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "TagLibSharp": { + "target": "Package", + "version": "[2.3.0, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "projectName": "Jellyfin.Data", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Logging": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj", + "projectName": "Jellyfin.Common", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.AspNetCore": "(,10.0.32767]", + "Microsoft.AspNetCore.Antiforgery": "(,10.0.32767]", + "Microsoft.AspNetCore.App": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.BearerToken": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Cookies": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.OAuth": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization.Policy": "(,10.0.32767]", + "Microsoft.AspNetCore.Components": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Endpoints": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Forms": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Server": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Web": "(,10.0.32767]", + "Microsoft.AspNetCore.Connections.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.CookiePolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.Internal": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.AspNetCore.HostFiltering": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Html.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Features": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Results": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpLogging": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpOverrides": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpsPolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Identity": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Metadata": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ApiExplorer": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.DataAnnotations": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Xml": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.RazorPages": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.TagHelpers": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ViewFeatures": "(,10.0.32767]", + "Microsoft.AspNetCore.OutputCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.RateLimiting": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor.Runtime": "(,10.0.32767]", + "Microsoft.AspNetCore.RequestDecompression": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCompression": "(,10.0.32767]", + "Microsoft.AspNetCore.Rewrite": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.HttpSys": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IIS": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IISIntegration": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets": "(,10.0.32767]", + "Microsoft.AspNetCore.Session": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticAssets": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticFiles": "(,10.0.32767]", + "Microsoft.AspNetCore.WebSockets": "(,10.0.32767]", + "Microsoft.AspNetCore.WebUtilities": "(,10.0.32767]", + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.Extensions.Caching.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Caching.Memory": "(,10.0.32767]", + "Microsoft.Extensions.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Binder": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.CommandLine": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.FileExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Ini": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Json": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.KeyPerFile": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.UserSecrets": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Xml": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Features": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Composite": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Physical": "(,10.0.32767]", + "Microsoft.Extensions.FileSystemGlobbing": "(,10.0.32767]", + "Microsoft.Extensions.Hosting": "(,10.0.32767]", + "Microsoft.Extensions.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Http": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Core": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Stores": "(,10.0.32767]", + "Microsoft.Extensions.Localization": "(,10.0.32767]", + "Microsoft.Extensions.Localization.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Console": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Debug": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventLog": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventSource": "(,10.0.32767]", + "Microsoft.Extensions.Logging.TraceSource": "(,10.0.32767]", + "Microsoft.Extensions.ObjectPool": "(,10.0.32767]", + "Microsoft.Extensions.Options": "(,10.0.32767]", + "Microsoft.Extensions.Options.ConfigurationExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Options.DataAnnotations": "(,10.0.32767]", + "Microsoft.Extensions.Primitives": "(,10.0.32767]", + "Microsoft.Extensions.Validation": "(,10.0.32767]", + "Microsoft.Extensions.WebEncoders": "(,10.0.32767]", + "Microsoft.JSInterop": "(,10.0.32767]", + "Microsoft.Net.Http.Headers": "(,10.0.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.EventLog": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Cbor": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Cryptography.Xml": "(,10.0.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.RateLimiting": "(,10.0.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj", + "projectName": "Jellyfin.Controller", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "BitFaster.Caching": { + "target": "Package", + "version": "[2.5.4, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Configuration.Binder": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj", + "projectName": "Jellyfin.Model", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "MimeTypes": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[2.5.2, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.AspNetCore": "(,10.0.32767]", + "Microsoft.AspNetCore.Antiforgery": "(,10.0.32767]", + "Microsoft.AspNetCore.App": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.BearerToken": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Cookies": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.OAuth": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization.Policy": "(,10.0.32767]", + "Microsoft.AspNetCore.Components": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Endpoints": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Forms": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Server": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Web": "(,10.0.32767]", + "Microsoft.AspNetCore.Connections.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.CookiePolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.Internal": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.AspNetCore.HostFiltering": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Html.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Features": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Results": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpLogging": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpOverrides": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpsPolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Identity": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Metadata": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ApiExplorer": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.DataAnnotations": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Xml": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.RazorPages": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.TagHelpers": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ViewFeatures": "(,10.0.32767]", + "Microsoft.AspNetCore.OutputCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.RateLimiting": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor.Runtime": "(,10.0.32767]", + "Microsoft.AspNetCore.RequestDecompression": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCompression": "(,10.0.32767]", + "Microsoft.AspNetCore.Rewrite": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.HttpSys": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IIS": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IISIntegration": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets": "(,10.0.32767]", + "Microsoft.AspNetCore.Session": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticAssets": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticFiles": "(,10.0.32767]", + "Microsoft.AspNetCore.WebSockets": "(,10.0.32767]", + "Microsoft.AspNetCore.WebUtilities": "(,10.0.32767]", + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.Extensions.Caching.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Caching.Memory": "(,10.0.32767]", + "Microsoft.Extensions.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Binder": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.CommandLine": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.FileExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Ini": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Json": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.KeyPerFile": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.UserSecrets": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Xml": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Features": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Composite": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Physical": "(,10.0.32767]", + "Microsoft.Extensions.FileSystemGlobbing": "(,10.0.32767]", + "Microsoft.Extensions.Hosting": "(,10.0.32767]", + "Microsoft.Extensions.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Http": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Core": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Stores": "(,10.0.32767]", + "Microsoft.Extensions.Localization": "(,10.0.32767]", + "Microsoft.Extensions.Localization.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Console": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Debug": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventLog": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventSource": "(,10.0.32767]", + "Microsoft.Extensions.Logging.TraceSource": "(,10.0.32767]", + "Microsoft.Extensions.ObjectPool": "(,10.0.32767]", + "Microsoft.Extensions.Options": "(,10.0.32767]", + "Microsoft.Extensions.Options.ConfigurationExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Options.DataAnnotations": "(,10.0.32767]", + "Microsoft.Extensions.Primitives": "(,10.0.32767]", + "Microsoft.Extensions.Validation": "(,10.0.32767]", + "Microsoft.Extensions.WebEncoders": "(,10.0.32767]", + "Microsoft.JSInterop": "(,10.0.32767]", + "Microsoft.Net.Http.Headers": "(,10.0.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.EventLog": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Cbor": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Cryptography.Xml": "(,10.0.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.RateLimiting": "(,10.0.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "projectName": "Jellyfin.CodeAnalysis", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "netstandard2.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "netstandard2.0": { + "targetAlias": "netstandard2.0", + "projectReferences": {} + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "netstandard2.0": { + "targetAlias": "netstandard2.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[3.11.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.CSharp": { + "suppressParent": "All", + "target": "Package", + "version": "[5.0.0, )", + "versionCentrallyManaged": true + }, + "NETStandard.Library": { + "suppressParent": "All", + "target": "Package", + "version": "[2.0.3, )", + "autoReferenced": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/RuntimeIdentifierGraph.json" + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "version": "10.11.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "projectName": "Jellyfin.Database.Implementations", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Design": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Relational": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Tools": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Polly": { + "target": "Package", + "version": "[8.6.5, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "projectName": "Jellyfin.Extensions", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "noWarn": [ + "NU5104" + ], + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "Diacritics": { + "target": "Package", + "version": "[4.1.4, )", + "versionCentrallyManaged": true + }, + "ICU4N.Transliterator": { + "target": "Package", + "version": "[60.1.0-alpha.356, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj": { + "version": "10.11.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj", + "projectName": "Jellyfin.MediaEncoding.Keyframes", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "NEbml": { + "target": "Package", + "version": "[1.1.0.5, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + } + } +} \ No newline at end of file diff --git a/Emby.Photos/obj/Emby.Photos.csproj.nuget.g.props b/Emby.Photos/obj/Emby.Photos.csproj.nuget.g.props new file mode 100644 index 00000000..aa204e43 --- /dev/null +++ b/Emby.Photos/obj/Emby.Photos.csproj.nuget.g.props @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess> + <RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool> + <ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile> + <NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageRoot> + <NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageFolders> + <NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle> + <NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion> + </PropertyGroup> + <ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <SourceRoot Include="/home/wjones/.nuget/packages/" /> + </ItemGroup> + <ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props')" /> + <Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props')" /> + </ImportGroup> + <PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <PkgStyleCop_Analyzers_Unstable Condition=" '$(PkgStyleCop_Analyzers_Unstable)' == '' ">/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556</PkgStyleCop_Analyzers_Unstable> + <PkgSmartAnalyzers_MultithreadingAnalyzer Condition=" '$(PkgSmartAnalyzers_MultithreadingAnalyzer)' == '' ">/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31</PkgSmartAnalyzers_MultithreadingAnalyzer> + <PkgSerilogAnalyzer Condition=" '$(PkgSerilogAnalyzer)' == '' ">/home/wjones/.nuget/packages/seriloganalyzer/0.15.0</PkgSerilogAnalyzer> + <PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers Condition=" '$(PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers)' == '' ">/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0</PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers> + <PkgIDisposableAnalyzers Condition=" '$(PkgIDisposableAnalyzers)' == '' ">/home/wjones/.nuget/packages/idisposableanalyzers/4.0.8</PkgIDisposableAnalyzers> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/Emby.Photos/obj/Emby.Photos.csproj.nuget.g.targets b/Emby.Photos/obj/Emby.Photos.csproj.nuget.g.targets new file mode 100644 index 00000000..85986725 --- /dev/null +++ b/Emby.Photos/obj/Emby.Photos.csproj.nuget.g.targets @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <Import Project="$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.binder/10.0.3/buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.binder/10.0.3/buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets')" /> + </ImportGroup> +</Project> \ No newline at end of file diff --git a/Emby.Photos/obj/project.assets.json b/Emby.Photos/obj/project.assets.json new file mode 100644 index 00000000..1b5c8bdc --- /dev/null +++ b/Emby.Photos/obj/project.assets.json @@ -0,0 +1,1911 @@ +{ + "version": 3, + "targets": { + "net10.0": { + "BitFaster.Caching/2.5.4": { + "type": "package", + "compile": { + "lib/net6.0/BitFaster.Caching.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/BitFaster.Caching.dll": { + "related": ".xml" + } + } + }, + "Diacritics/4.1.4": { + "type": "package", + "compile": { + "lib/net9.0/Diacritics.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Diacritics.dll": { + "related": ".xml" + } + } + }, + "ICU4N/60.1.0-alpha.356": { + "type": "package", + "dependencies": { + "J2N": "2.0.0", + "Microsoft.Extensions.Caching.Memory": "2.0.0" + }, + "compile": { + "lib/netstandard2.0/ICU4N.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/ICU4N.dll": { + "related": ".xml" + } + } + }, + "ICU4N.Transliterator/60.1.0-alpha.356": { + "type": "package", + "dependencies": { + "ICU4N": "60.1.0-alpha.356" + }, + "compile": { + "lib/netstandard2.0/ICU4N.Transliterator.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/ICU4N.Transliterator.dll": { + "related": ".xml" + } + } + }, + "IDisposableAnalyzers/4.0.8": { + "type": "package" + }, + "J2N/2.0.0": { + "type": "package", + "compile": { + "lib/net6.0/J2N.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/J2N.dll": { + "related": ".xml" + } + } + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers/4.14.0": { + "type": "package", + "build": { + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props": {}, + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets": {} + } + }, + "Microsoft.EntityFrameworkCore/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "10.0.3", + "Microsoft.EntityFrameworkCore.Analyzers": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props": {} + } + }, + "Microsoft.EntityFrameworkCore.Abstractions/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + } + }, + "Microsoft.EntityFrameworkCore.Analyzers/10.0.3": { + "type": "package" + }, + "Microsoft.EntityFrameworkCore.Relational/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + } + }, + "Microsoft.Extensions.Caching.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Caching.Memory/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Caching.Memory.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Caching.Memory.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Configuration/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Configuration.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Configuration.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Configuration.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Configuration.Binder/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Configuration.Binder.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Configuration.Binder.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets": {} + } + }, + "Microsoft.Extensions.DependencyInjection/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Logging/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Logging.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Logging.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Logging.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets": {} + } + }, + "Microsoft.Extensions.Options/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Options.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Options.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/Microsoft.Extensions.Options.targets": {} + } + }, + "Microsoft.Extensions.Primitives/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.Extensions.Primitives.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Primitives.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "NEbml/1.1.0.5": { + "type": "package", + "compile": { + "lib/netstandard2.0/NEbml.Core.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/NEbml.Core.dll": { + "related": ".xml" + } + } + }, + "Polly/8.6.5": { + "type": "package", + "dependencies": { + "Polly.Core": "8.6.5" + }, + "compile": { + "lib/net6.0/Polly.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net6.0/Polly.dll": { + "related": ".pdb;.xml" + } + } + }, + "Polly.Core/8.6.5": { + "type": "package", + "compile": { + "lib/net8.0/Polly.Core.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net8.0/Polly.Core.dll": { + "related": ".pdb;.xml" + } + } + }, + "SerilogAnalyzer/0.15.0": { + "type": "package" + }, + "SmartAnalyzers.MultithreadingAnalyzer/1.1.31": { + "type": "package" + }, + "StyleCop.Analyzers/1.2.0-beta.556": { + "type": "package", + "dependencies": { + "StyleCop.Analyzers.Unstable": "1.2.0.556" + } + }, + "StyleCop.Analyzers.Unstable/1.2.0.556": { + "type": "package" + }, + "TagLibSharp/2.3.0": { + "type": "package", + "compile": { + "lib/netstandard2.0/TagLibSharp.dll": { + "related": ".pdb" + } + }, + "runtime": { + "lib/netstandard2.0/TagLibSharp.dll": { + "related": ".pdb" + } + } + }, + "Jellyfin.CodeAnalysis/1.0.0": { + "type": "project", + "framework": ".NETStandard,Version=v2.0", + "compile": { + "bin/placeholder/Jellyfin.CodeAnalysis.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.CodeAnalysis.dll": {} + } + }, + "Jellyfin.Common/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Common.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Common.dll": {} + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Jellyfin.Controller/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "BitFaster.Caching": "2.5.4", + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.MediaEncoding.Keyframes": "10.11.0", + "Jellyfin.Model": "10.12.0", + "Jellyfin.Naming": "10.12.0", + "Microsoft.Extensions.Configuration.Binder": "10.0.3" + }, + "compile": { + "bin/placeholder/Jellyfin.Controller.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Controller.dll": {} + } + }, + "Jellyfin.Data/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Database.Implementations": "10.11.0", + "Microsoft.Extensions.Logging": "10.0.3" + }, + "compile": { + "bin/placeholder/Jellyfin.Data.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Data.dll": {} + } + }, + "Jellyfin.Database.Implementations/10.11.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Polly": "8.6.5" + }, + "compile": { + "bin/placeholder/Jellyfin.Database.Implementations.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Database.Implementations.dll": {} + } + }, + "Jellyfin.Extensions/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Diacritics": "4.1.4", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "Jellyfin.CodeAnalysis": "1.0.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Extensions.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Extensions.dll": {} + } + }, + "Jellyfin.MediaEncoding.Keyframes/10.11.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "NEbml": "1.1.0.5" + }, + "compile": { + "bin/placeholder/Jellyfin.MediaEncoding.Keyframes.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.MediaEncoding.Keyframes.dll": {} + } + }, + "Jellyfin.Model/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Data": "10.12.0", + "Jellyfin.Extensions": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Model.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Model.dll": {} + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Jellyfin.Naming/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Naming.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Naming.dll": {} + } + } + } + }, + "libraries": { + "BitFaster.Caching/2.5.4": { + "sha512": "1QroTY1PVCZOSG9FnkkCrmCKk/+bZCgI/YXq376HnYwUDJ4Ho0EaV4YaA/5v5WYLnwIwIO7RZkdWbg9pxIpueQ==", + "type": "package", + "path": "bitfaster.caching/2.5.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "ReadMe.md", + "bitfaster.caching.2.5.4.nupkg.sha512", + "bitfaster.caching.nuspec", + "lib/net6.0/BitFaster.Caching.dll", + "lib/net6.0/BitFaster.Caching.xml", + "lib/netcoreapp3.1/BitFaster.Caching.dll", + "lib/netcoreapp3.1/BitFaster.Caching.xml", + "lib/netstandard2.0/BitFaster.Caching.dll", + "lib/netstandard2.0/BitFaster.Caching.xml" + ] + }, + "Diacritics/4.1.4": { + "sha512": "9JK2bI+oWnzOK4Lu+5FXt7czofD28AMnZ95np5fkSClqioPnga1JVpjaUxVJaEpc/5HIlfikc6YI33lm2qu4mw==", + "type": "package", + "path": "diacritics/4.1.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "README.md", + "diacritics.4.1.4.nupkg.sha512", + "diacritics.nuspec", + "lib/net48/Diacritics.dll", + "lib/net48/Diacritics.xml", + "lib/net7.0/Diacritics.dll", + "lib/net7.0/Diacritics.xml", + "lib/net8.0/Diacritics.dll", + "lib/net8.0/Diacritics.xml", + "lib/net9.0/Diacritics.dll", + "lib/net9.0/Diacritics.xml", + "lib/netstandard1.2/Diacritics.dll", + "lib/netstandard1.2/Diacritics.xml", + "lib/netstandard2.0/Diacritics.dll", + "lib/netstandard2.0/Diacritics.xml", + "lib/netstandard2.1/Diacritics.dll", + "lib/netstandard2.1/Diacritics.xml", + "logo.png" + ] + }, + "ICU4N/60.1.0-alpha.356": { + "sha512": "YMZtDnjcqWzziOKiE7w6Ma7Rl5vuFDxzOsUlHh1QyfghbNEIZQOLRs9MMfwCWAjX6n9UitrF6vLXy55Z5q+4Fg==", + "type": "package", + "path": "icu4n/60.1.0-alpha.356", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "icu4n.60.1.0-alpha.356.nupkg.sha512", + "icu4n.nuspec", + "lib/net40/ICU4N.dll", + "lib/net40/ICU4N.xml", + "lib/net451/ICU4N.dll", + "lib/net451/ICU4N.xml", + "lib/netstandard2.0/ICU4N.dll", + "lib/netstandard2.0/ICU4N.xml", + "unicode-icon-128x128.jpg" + ] + }, + "ICU4N.Transliterator/60.1.0-alpha.356": { + "sha512": "lFOSO6bbEtB6HkWMNDJAq+rFwVyi9g6xVc5O/2xHa6iZnV7wLVDqCbaQ4W4vIeBSQZAafqhxciaEkmAvSdzlCg==", + "type": "package", + "path": "icu4n.transliterator/60.1.0-alpha.356", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "icu4n.transliterator.60.1.0-alpha.356.nupkg.sha512", + "icu4n.transliterator.nuspec", + "lib/net40/ICU4N.Transliterator.dll", + "lib/net40/ICU4N.Transliterator.xml", + "lib/net451/ICU4N.Transliterator.dll", + "lib/net451/ICU4N.Transliterator.xml", + "lib/netstandard2.0/ICU4N.Transliterator.dll", + "lib/netstandard2.0/ICU4N.Transliterator.xml", + "unicode-icon-128x128.jpg" + ] + }, + "IDisposableAnalyzers/4.0.8": { + "sha512": "vNi4NMG0CcJyjXxiNDcQ21FwV/whM9o9OEZKD+oP7tuxAqFEzX/x5OhC3OZJqW/w+8GOtCmJPBquYgMWgz0rfQ==", + "type": "package", + "path": "idisposableanalyzers/4.0.8", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/IDisposableAnalyzers.dll", + "idisposableanalyzers.4.0.8.nupkg.sha512", + "idisposableanalyzers.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "J2N/2.0.0": { + "sha512": "M5bwDajAARZiyqupU+rHQJnsVLxNBOHJ8vKYHd8LcLIb1FgLfzzcJvc31Qo5Xz/GEHFjDF9ScjKL/ks/zRTXuA==", + "type": "package", + "path": "j2n/2.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "j2n-icon-100x100.png", + "j2n.2.0.0.nupkg.sha512", + "j2n.nuspec", + "lib/net40/J2N.dll", + "lib/net40/J2N.xml", + "lib/net45/J2N.dll", + "lib/net45/J2N.xml", + "lib/net5.0/J2N.dll", + "lib/net5.0/J2N.xml", + "lib/net6.0/J2N.dll", + "lib/net6.0/J2N.xml", + "lib/netcoreapp3.0/J2N.dll", + "lib/netcoreapp3.0/J2N.xml", + "lib/netstandard2.0/J2N.dll", + "lib/netstandard2.0/J2N.xml", + "lib/netstandard2.1/J2N.dll", + "lib/netstandard2.1/J2N.xml", + "readme.md" + ] + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers/4.14.0": { + "sha512": "gSWJlDWwmDhtbrEJGiHqvEjz9KthIiFD0qYB8zZ6a7z+xpMSPEtM9yTYELSa58iFWYlzSRqP9FXO6KoT3+ZMtg==", + "type": "package", + "path": "microsoft.codeanalysis.bannedapianalyzers/4.14.0", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.txt", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.dll", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.CSharp.BannedApiAnalyzers.dll", + "analyzers/dotnet/cs/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/de/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/es/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/fr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/it/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ja/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ko/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/pl/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/pt-BR/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ru/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/tr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hans/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hant/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.BannedApiAnalyzers.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.VisualBasic.BannedApiAnalyzers.dll", + "analyzers/dotnet/vb/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/de/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/es/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/fr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/it/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ja/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ko/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/pl/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/pt-BR/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ru/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/tr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hans/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hant/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props", + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets", + "documentation/Microsoft.CodeAnalysis.BannedApiAnalyzers.md", + "documentation/Microsoft.CodeAnalysis.BannedApiAnalyzers.sarif", + "editorconfig/AllRulesDefault/.editorconfig", + "editorconfig/AllRulesDisabled/.editorconfig", + "editorconfig/AllRulesEnabled/.editorconfig", + "editorconfig/ApiDesignRulesDefault/.editorconfig", + "editorconfig/ApiDesignRulesEnabled/.editorconfig", + "editorconfig/DataflowRulesDefault/.editorconfig", + "editorconfig/DataflowRulesEnabled/.editorconfig", + "editorconfig/PortedFromFxCopRulesDefault/.editorconfig", + "editorconfig/PortedFromFxCopRulesEnabled/.editorconfig", + "microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512", + "microsoft.codeanalysis.bannedapianalyzers.nuspec", + "rulesets/AllRulesDefault.ruleset", + "rulesets/AllRulesDisabled.ruleset", + "rulesets/AllRulesEnabled.ruleset", + "rulesets/ApiDesignRulesDefault.ruleset", + "rulesets/ApiDesignRulesEnabled.ruleset", + "rulesets/DataflowRulesDefault.ruleset", + "rulesets/DataflowRulesEnabled.ruleset", + "rulesets/PortedFromFxCopRulesDefault.ruleset", + "rulesets/PortedFromFxCopRulesEnabled.ruleset", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Microsoft.EntityFrameworkCore/10.0.3": { + "sha512": "CDEImwD4A7BseABJMCpLZnhfFjmPY/bHwhhS70elc6gLI/bYUEOhxWt7PmaNGYGhIEzOnStlCy5QcVb+8dod5Q==", + "type": "package", + "path": "microsoft.entityframeworkcore/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props", + "lib/net10.0/Microsoft.EntityFrameworkCore.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.xml", + "microsoft.entityframeworkcore.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Abstractions/10.0.3": { + "sha512": "c7Uoz381xnMHNBRB8eHRhGgzUtXbgddlbODhwZRrTSzZWDharp3RkJsFwhxyESbeXhCqmML7VdvjMQ7uu+HreA==", + "type": "package", + "path": "microsoft.entityframeworkcore.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.xml", + "microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.abstractions.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Analyzers/10.0.3": { + "sha512": "lVABgJTyTUNE7Bi0bSu4dWHiCHIXEGzTh/kLh0N07IgU/tIKwTeBPp8tgV4x2iKj4h7iPLo8oXzyHmLDGtAE1g==", + "type": "package", + "path": "microsoft.entityframeworkcore.analyzers/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "analyzers/dotnet/cs/Microsoft.EntityFrameworkCore.Analyzers.dll", + "docs/PACKAGE.md", + "microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.analyzers.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Relational/10.0.3": { + "sha512": "Pmh60OK9neVr/M0FJwm9hlzm2bD4Kd65SID8E6SP5c90tExNgXwORrlEWl0oGU/ig9ifpNN4PSpIrnHNozlT5w==", + "type": "package", + "path": "microsoft.entityframeworkcore.relational/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.xml", + "microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.relational.nuspec" + ] + }, + "Microsoft.Extensions.Caching.Abstractions/10.0.3": { + "sha512": "5dtXBvI8t3z8pF4tB38JYgi/enCL/DwSXxpqShgFz3SHJ7IzqFIMs6Gu5ik8sNZzcO9qQs3xIDpB3vDamkYG+Q==", + "type": "package", + "path": "microsoft.extensions.caching.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Caching.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Caching.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.xml", + "microsoft.extensions.caching.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.caching.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Caching.Memory/10.0.3": { + "sha512": "No4fVh0z30SWqiWFRoA4PNdrEco6OjXvCqRFvlmRgDQqqks2bRDdeavUgWEiAX153ZAwW9loUgbxcvuP4NKQLg==", + "type": "package", + "path": "microsoft.extensions.caching.memory/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Caching.Memory.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Caching.Memory.targets", + "lib/net10.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net10.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/net462/Microsoft.Extensions.Caching.Memory.dll", + "lib/net462/Microsoft.Extensions.Caching.Memory.xml", + "lib/net8.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net8.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/net9.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net9.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.xml", + "microsoft.extensions.caching.memory.10.0.3.nupkg.sha512", + "microsoft.extensions.caching.memory.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Configuration/10.0.3": { + "sha512": "H1Cjv2xmm7O3iAGmFTcnSM0ZhLQ/7SqefmAvSJoT1PbXoxeYc2fo0mCLn2JlVbr9E6YpoU9q/o0fI9neDJB0xQ==", + "type": "package", + "path": "microsoft.extensions.configuration/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Configuration.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Configuration.targets", + "lib/net10.0/Microsoft.Extensions.Configuration.dll", + "lib/net10.0/Microsoft.Extensions.Configuration.xml", + "lib/net462/Microsoft.Extensions.Configuration.dll", + "lib/net462/Microsoft.Extensions.Configuration.xml", + "lib/net8.0/Microsoft.Extensions.Configuration.dll", + "lib/net8.0/Microsoft.Extensions.Configuration.xml", + "lib/net9.0/Microsoft.Extensions.Configuration.dll", + "lib/net9.0/Microsoft.Extensions.Configuration.xml", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.dll", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.xml", + "microsoft.extensions.configuration.10.0.3.nupkg.sha512", + "microsoft.extensions.configuration.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Configuration.Abstractions/10.0.3": { + "sha512": "xVDHL0+SIgemfh95fTO9cGLe17TWv/ZP0n7m01z8X6pzt2DmQpucioWR/mYZA1sRlkWnkXzfl0JweLNWmE9WMg==", + "type": "package", + "path": "microsoft.extensions.configuration.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Configuration.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Configuration.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "microsoft.extensions.configuration.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.configuration.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Configuration.Binder/10.0.3": { + "sha512": "759UhpKaR5Jsll9kXpkft4z/7tpeF7Dw2rTY/9f9JchaSQTpRFNIPkZFZvoo7fFpbjUaqtDlO5aiGpmQrp/EUA==", + "type": "package", + "path": "microsoft.extensions.configuration.binder/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/cs/Microsoft.Extensions.Configuration.Binder.SourceGeneration.dll", + "analyzers/dotnet/cs/cs/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/de/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/es/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/fr/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/it/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/ja/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/ko/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/pl/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/pt-BR/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/ru/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/tr/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/zh-Hans/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/zh-Hant/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets", + "lib/net10.0/Microsoft.Extensions.Configuration.Binder.dll", + "lib/net10.0/Microsoft.Extensions.Configuration.Binder.xml", + "lib/net462/Microsoft.Extensions.Configuration.Binder.dll", + "lib/net462/Microsoft.Extensions.Configuration.Binder.xml", + "lib/net8.0/Microsoft.Extensions.Configuration.Binder.dll", + "lib/net8.0/Microsoft.Extensions.Configuration.Binder.xml", + "lib/net9.0/Microsoft.Extensions.Configuration.Binder.dll", + "lib/net9.0/Microsoft.Extensions.Configuration.Binder.xml", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Binder.dll", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Binder.xml", + "microsoft.extensions.configuration.binder.10.0.3.nupkg.sha512", + "microsoft.extensions.configuration.binder.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyInjection/10.0.3": { + "sha512": "2DLOmC0EkB2smVK8lPP1PIKEgL1arE3CMp9XSIQB/Y7ev5nnnyuM/PizKJ6QfLD08QCYoopSC9SFdbYglDomYg==", + "type": "package", + "path": "microsoft.extensions.dependencyinjection/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyInjection.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.targets", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/net462/Microsoft.Extensions.DependencyInjection.dll", + "lib/net462/Microsoft.Extensions.DependencyInjection.xml", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.dll", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.xml", + "microsoft.extensions.dependencyinjection.10.0.3.nupkg.sha512", + "microsoft.extensions.dependencyinjection.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/10.0.3": { + "sha512": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==", + "type": "package", + "path": "microsoft.extensions.dependencyinjection.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyInjection.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "microsoft.extensions.dependencyinjection.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.dependencyinjection.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Logging/10.0.3": { + "sha512": "8D9Er1cGXNjNDIB+VLBNHn386L5ls2FoiG9a6o12gyn+GG3w6jdfUhzT8dtBnKcevE7/fsVA8MS3FBgFfClFtQ==", + "type": "package", + "path": "microsoft.extensions.logging/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Logging.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Logging.targets", + "lib/net10.0/Microsoft.Extensions.Logging.dll", + "lib/net10.0/Microsoft.Extensions.Logging.xml", + "lib/net462/Microsoft.Extensions.Logging.dll", + "lib/net462/Microsoft.Extensions.Logging.xml", + "lib/net8.0/Microsoft.Extensions.Logging.dll", + "lib/net8.0/Microsoft.Extensions.Logging.xml", + "lib/net9.0/Microsoft.Extensions.Logging.dll", + "lib/net9.0/Microsoft.Extensions.Logging.xml", + "lib/netstandard2.0/Microsoft.Extensions.Logging.dll", + "lib/netstandard2.0/Microsoft.Extensions.Logging.xml", + "lib/netstandard2.1/Microsoft.Extensions.Logging.dll", + "lib/netstandard2.1/Microsoft.Extensions.Logging.xml", + "microsoft.extensions.logging.10.0.3.nupkg.sha512", + "microsoft.extensions.logging.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Logging.Abstractions/10.0.3": { + "sha512": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", + "type": "package", + "path": "microsoft.extensions.logging.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn3.11/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn3.11/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn4.0/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "buildTransitive/net461/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/net462/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.xml", + "microsoft.extensions.logging.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.logging.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Options/10.0.3": { + "sha512": "hU6WzGTPvPoLA2ng1ILvWQb3g0qORdlHNsxI8IcPLumJb3suimYUl+bbDzdo1V4KFsvVhnMWzysHpKbZaoDQPQ==", + "type": "package", + "path": "microsoft.extensions.options/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Options.SourceGeneration.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "buildTransitive/net461/Microsoft.Extensions.Options.targets", + "buildTransitive/net462/Microsoft.Extensions.Options.targets", + "buildTransitive/net8.0/Microsoft.Extensions.Options.targets", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Options.targets", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Options.targets", + "lib/net10.0/Microsoft.Extensions.Options.dll", + "lib/net10.0/Microsoft.Extensions.Options.xml", + "lib/net462/Microsoft.Extensions.Options.dll", + "lib/net462/Microsoft.Extensions.Options.xml", + "lib/net8.0/Microsoft.Extensions.Options.dll", + "lib/net8.0/Microsoft.Extensions.Options.xml", + "lib/net9.0/Microsoft.Extensions.Options.dll", + "lib/net9.0/Microsoft.Extensions.Options.xml", + "lib/netstandard2.0/Microsoft.Extensions.Options.dll", + "lib/netstandard2.0/Microsoft.Extensions.Options.xml", + "lib/netstandard2.1/Microsoft.Extensions.Options.dll", + "lib/netstandard2.1/Microsoft.Extensions.Options.xml", + "microsoft.extensions.options.10.0.3.nupkg.sha512", + "microsoft.extensions.options.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Primitives/10.0.3": { + "sha512": "GEcpTwo7sUoLGGNTqV1FZEuL+tTD9m81NX/mh099dqGNna07/UGZShKQNZRw4hv6nlliSUwYQgSYc7OR99Jufg==", + "type": "package", + "path": "microsoft.extensions.primitives/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Primitives.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Primitives.targets", + "lib/net10.0/Microsoft.Extensions.Primitives.dll", + "lib/net10.0/Microsoft.Extensions.Primitives.xml", + "lib/net462/Microsoft.Extensions.Primitives.dll", + "lib/net462/Microsoft.Extensions.Primitives.xml", + "lib/net8.0/Microsoft.Extensions.Primitives.dll", + "lib/net8.0/Microsoft.Extensions.Primitives.xml", + "lib/net9.0/Microsoft.Extensions.Primitives.dll", + "lib/net9.0/Microsoft.Extensions.Primitives.xml", + "lib/netstandard2.0/Microsoft.Extensions.Primitives.dll", + "lib/netstandard2.0/Microsoft.Extensions.Primitives.xml", + "microsoft.extensions.primitives.10.0.3.nupkg.sha512", + "microsoft.extensions.primitives.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "NEbml/1.1.0.5": { + "sha512": "svtqDc+hue9kbnqNN2KkK4om/hDrc7K127cNb5FIYfgKgzo+JNDPXNLp8NioCchHhBO3lxWd4Cp/iiZZ3aoUqg==", + "type": "package", + "path": "nebml/1.1.0.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net461/NEbml.Core.dll", + "lib/net461/NEbml.Core.xml", + "lib/netstandard2.0/NEbml.Core.dll", + "lib/netstandard2.0/NEbml.Core.xml", + "nebml.1.1.0.5.nupkg.sha512", + "nebml.nuspec" + ] + }, + "Polly/8.6.5": { + "sha512": "VqtW2ZE/ALvQMAH1cQY3qZ2cF2OXa3oe/HKMdOv6Q02HCoEW0rsFNfcBONXlHBe1TnjWW1vdRxBEkPeq0/2FHA==", + "type": "package", + "path": "polly/8.6.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "lib/net462/Polly.dll", + "lib/net462/Polly.pdb", + "lib/net462/Polly.xml", + "lib/net472/Polly.dll", + "lib/net472/Polly.pdb", + "lib/net472/Polly.xml", + "lib/net6.0/Polly.dll", + "lib/net6.0/Polly.pdb", + "lib/net6.0/Polly.xml", + "lib/netstandard2.0/Polly.dll", + "lib/netstandard2.0/Polly.pdb", + "lib/netstandard2.0/Polly.xml", + "package-icon.png", + "package-readme.md", + "polly.8.6.5.nupkg.sha512", + "polly.nuspec" + ] + }, + "Polly.Core/8.6.5": { + "sha512": "t+sUVrIwvo7UmsgHGgOG9F0GDZSRIm47u2ylH17Gvcv1q5hNEwgD5GoBlFyc0kh/pebmPyrAgvGsR/65ZBaXlg==", + "type": "package", + "path": "polly.core/8.6.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "lib/net462/Polly.Core.dll", + "lib/net462/Polly.Core.pdb", + "lib/net462/Polly.Core.xml", + "lib/net472/Polly.Core.dll", + "lib/net472/Polly.Core.pdb", + "lib/net472/Polly.Core.xml", + "lib/net6.0/Polly.Core.dll", + "lib/net6.0/Polly.Core.pdb", + "lib/net6.0/Polly.Core.xml", + "lib/net8.0/Polly.Core.dll", + "lib/net8.0/Polly.Core.pdb", + "lib/net8.0/Polly.Core.xml", + "lib/netstandard2.0/Polly.Core.dll", + "lib/netstandard2.0/Polly.Core.pdb", + "lib/netstandard2.0/Polly.Core.xml", + "package-icon.png", + "package-readme.md", + "polly.core.8.6.5.nupkg.sha512", + "polly.core.nuspec" + ] + }, + "SerilogAnalyzer/0.15.0": { + "sha512": "sVpwfls4MfNnwIXLSGCgaUnV+c9kgJ8ia6GsyRcpd4Vs3gLogSDtSYBYrre2K2u/PNMo8GgG09RehwVnze70Tw==", + "type": "package", + "path": "seriloganalyzer/0.15.0", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/SerilogAnalyzer.dll", + "seriloganalyzer.0.15.0.nupkg.sha512", + "seriloganalyzer.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "SmartAnalyzers.MultithreadingAnalyzer/1.1.31": { + "sha512": "2f2k7bbhDd132ArglCKpzKoWBcp3uzbIFcb4aosnlqIKlfYKDE2HevBVRNVa+LkWFnjXFFWs47Bo96fu8iS//Q==", + "type": "package", + "path": "smartanalyzers.multithreadinganalyzer/1.1.31", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/SmartAnalyzers.MultithreadingAnalyzer.dll", + "smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512", + "smartanalyzers.multithreadinganalyzer.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "StyleCop.Analyzers/1.2.0-beta.556": { + "sha512": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", + "type": "package", + "path": "stylecop.analyzers/1.2.0-beta.556", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "THIRD-PARTY-NOTICES.txt", + "stylecop.analyzers.1.2.0-beta.556.nupkg.sha512", + "stylecop.analyzers.nuspec" + ] + }, + "StyleCop.Analyzers.Unstable/1.2.0.556": { + "sha512": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==", + "type": "package", + "path": "stylecop.analyzers.unstable/1.2.0.556", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "THIRD-PARTY-NOTICES.txt", + "analyzers/dotnet/cs/StyleCop.Analyzers.CodeFixes.dll", + "analyzers/dotnet/cs/StyleCop.Analyzers.dll", + "analyzers/dotnet/cs/de-DE/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/en-GB/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/es-MX/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/fr-FR/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/pl-PL/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/pt-BR/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/ru-RU/StyleCop.Analyzers.resources.dll", + "rulesets/StyleCopAnalyzersDefault.ruleset", + "stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512", + "stylecop.analyzers.unstable.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "TagLibSharp/2.3.0": { + "sha512": "Qo4z6ZjnIfbR3Us1Za5M2vQ97OWZPmODvVmepxZ8XW0UIVLGdO2T63/N3b23kCcyiwuIe0TQvMEQG8wUCCD1mA==", + "type": "package", + "path": "taglibsharp/2.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net462/TagLibSharp.dll", + "lib/net462/TagLibSharp.pdb", + "lib/net462/TaglibSharp.xml", + "lib/netstandard2.0/TagLibSharp.dll", + "lib/netstandard2.0/TagLibSharp.pdb", + "lib/netstandard2.0/TaglibSharp.xml", + "taglibsharp.2.3.0.nupkg.sha512", + "taglibsharp.nuspec" + ] + }, + "Jellyfin.CodeAnalysis/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "msbuildProject": "../src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "Jellyfin.Common/10.12.0": { + "type": "project", + "path": "../MediaBrowser.Common/MediaBrowser.Common.csproj", + "msbuildProject": "../MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "Jellyfin.Controller/10.12.0": { + "type": "project", + "path": "../MediaBrowser.Controller/MediaBrowser.Controller.csproj", + "msbuildProject": "../MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "Jellyfin.Data/10.12.0": { + "type": "project", + "path": "../Jellyfin.Data/Jellyfin.Data.csproj", + "msbuildProject": "../Jellyfin.Data/Jellyfin.Data.csproj" + }, + "Jellyfin.Database.Implementations/10.11.0": { + "type": "project", + "path": "../src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "msbuildProject": "../src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + }, + "Jellyfin.Extensions/10.12.0": { + "type": "project", + "path": "../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "msbuildProject": "../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" + }, + "Jellyfin.MediaEncoding.Keyframes/10.11.0": { + "type": "project", + "path": "../src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj", + "msbuildProject": "../src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj" + }, + "Jellyfin.Model/10.12.0": { + "type": "project", + "path": "../MediaBrowser.Model/MediaBrowser.Model.csproj", + "msbuildProject": "../MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "Jellyfin.Naming/10.12.0": { + "type": "project", + "path": "../Emby.Naming/Emby.Naming.csproj", + "msbuildProject": "../Emby.Naming/Emby.Naming.csproj" + } + }, + "projectFileDependencyGroups": { + "net10.0": [ + "IDisposableAnalyzers >= 4.0.8", + "Jellyfin.CodeAnalysis >= 1.0.0", + "Jellyfin.Controller >= 10.12.0", + "Jellyfin.Model >= 10.12.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers >= 4.14.0", + "SerilogAnalyzer >= 0.15.0", + "SmartAnalyzers.MultithreadingAnalyzer >= 1.1.31", + "StyleCop.Analyzers >= 1.2.0-beta.556", + "TagLibSharp >= 2.3.0" + ] + }, + "packageFolders": { + "/home/wjones/.nuget/packages/": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj", + "projectName": "Emby.Photos", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "TagLibSharp": { + "target": "Package", + "version": "[2.3.0, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + } +} \ No newline at end of file diff --git a/Emby.Photos/obj/project.nuget.cache b/Emby.Photos/obj/project.nuget.cache new file mode 100644 index 00000000..14950ff5 --- /dev/null +++ b/Emby.Photos/obj/project.nuget.cache @@ -0,0 +1,39 @@ +{ + "version": 2, + "dgSpecHash": "KcJoyPeqtMs=", + "success": true, + "projectFilePath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj", + "expectedPackageFiles": [ + "/home/wjones/.nuget/packages/bitfaster.caching/2.5.4/bitfaster.caching.2.5.4.nupkg.sha512", + "/home/wjones/.nuget/packages/diacritics/4.1.4/diacritics.4.1.4.nupkg.sha512", + "/home/wjones/.nuget/packages/icu4n/60.1.0-alpha.356/icu4n.60.1.0-alpha.356.nupkg.sha512", + "/home/wjones/.nuget/packages/icu4n.transliterator/60.1.0-alpha.356/icu4n.transliterator.60.1.0-alpha.356.nupkg.sha512", + "/home/wjones/.nuget/packages/idisposableanalyzers/4.0.8/idisposableanalyzers.4.0.8.nupkg.sha512", + "/home/wjones/.nuget/packages/j2n/2.0.0/j2n.2.0.0.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0/microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore/10.0.3/microsoft.entityframeworkcore.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.abstractions/10.0.3/microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.analyzers/10.0.3/microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.relational/10.0.3/microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.caching.abstractions/10.0.3/microsoft.extensions.caching.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.caching.memory/10.0.3/microsoft.extensions.caching.memory.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.configuration/10.0.3/microsoft.extensions.configuration.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.configuration.abstractions/10.0.3/microsoft.extensions.configuration.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.configuration.binder/10.0.3/microsoft.extensions.configuration.binder.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection/10.0.3/microsoft.extensions.dependencyinjection.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/10.0.3/microsoft.extensions.dependencyinjection.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.logging/10.0.3/microsoft.extensions.logging.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.logging.abstractions/10.0.3/microsoft.extensions.logging.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.options/10.0.3/microsoft.extensions.options.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.primitives/10.0.3/microsoft.extensions.primitives.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/nebml/1.1.0.5/nebml.1.1.0.5.nupkg.sha512", + "/home/wjones/.nuget/packages/polly/8.6.5/polly.8.6.5.nupkg.sha512", + "/home/wjones/.nuget/packages/polly.core/8.6.5/polly.core.8.6.5.nupkg.sha512", + "/home/wjones/.nuget/packages/seriloganalyzer/0.15.0/seriloganalyzer.0.15.0.nupkg.sha512", + "/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31/smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512", + "/home/wjones/.nuget/packages/stylecop.analyzers/1.2.0-beta.556/stylecop.analyzers.1.2.0-beta.556.nupkg.sha512", + "/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556/stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512", + "/home/wjones/.nuget/packages/taglibsharp/2.3.0/taglibsharp.2.3.0.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Emby.Photos/obj/project.packagespec.json b/Emby.Photos/obj/project.packagespec.json new file mode 100644 index 00000000..bf9bf706 --- /dev/null +++ b/Emby.Photos/obj/project.packagespec.json @@ -0,0 +1 @@ +"restore":{"projectUniqueName":"/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj","projectName":"Emby.Photos","projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj","outputPath":"/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/obj/","projectStyle":"PackageReference","centralPackageVersionsManagementEnabled":true,"originalTargetFrameworks":["net10.0"],"sources":{"/usr/lib/dotnet/library-packs":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net10.0":{"targetAlias":"net10.0","projectReferences":{"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj"}}}},"warningProperties":{"allWarningsAsErrors":true,"warnAsError":["NU1605"],"warnNotAsError":["NU1902","NU1903"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"all"},"SdkAnalysisLevel":"10.0.100"}"frameworks":{"net10.0":{"targetAlias":"net10.0","dependencies":{"IDisposableAnalyzers":{"include":"Runtime, Build, Native, ContentFiles, Analyzers","suppressParent":"All","target":"Package","version":"[4.0.8, )","versionCentrallyManaged":true},"Microsoft.CodeAnalysis.BannedApiAnalyzers":{"include":"Runtime, Build, Native, ContentFiles, Analyzers","suppressParent":"All","target":"Package","version":"[4.14.0, )","versionCentrallyManaged":true},"SerilogAnalyzer":{"suppressParent":"All","target":"Package","version":"[0.15.0, )","versionCentrallyManaged":true},"SmartAnalyzers.MultithreadingAnalyzer":{"suppressParent":"All","target":"Package","version":"[1.1.31, )","versionCentrallyManaged":true},"StyleCop.Analyzers":{"suppressParent":"All","target":"Package","version":"[1.2.0-beta.556, )","versionCentrallyManaged":true},"TagLibSharp":{"target":"Package","version":"[2.3.0, )","versionCentrallyManaged":true}},"centralPackageVersions":{"AsyncKeyedLock":"8.0.2","AutoFixture":"4.18.1","AutoFixture.AutoMoq":"4.18.1","AutoFixture.Xunit2":"4.18.1","BDInfo":"0.8.0","BitFaster.Caching":"2.5.4","BlurHashSharp":"1.4.0-pre.1","BlurHashSharp.SkiaSharp":"1.4.0-pre.1","CommandLineParser":"2.9.1","coverlet.collector":"8.0.0","Diacritics":"4.1.4","DiscUtils.Udf":"0.16.13","DotNet.Glob":"3.1.3","FsCheck.Xunit":"3.3.2","HarfBuzzSharp.NativeAssets.Linux":"8.3.1.1","ICU4N.Transliterator":"60.1.0-alpha.356","IDisposableAnalyzers":"4.0.8","Ignore":"0.2.1","Jellyfin.XmlTv":"10.8.0","libse":"4.0.12","LrcParser":"2025.623.0","MetaBrainz.MusicBrainz":"8.0.1","Microsoft.AspNetCore.Authorization":"10.0.3","Microsoft.AspNetCore.Mvc.Testing":"10.0.3","Microsoft.CodeAnalysis.Analyzers":"3.11.0","Microsoft.CodeAnalysis.BannedApiAnalyzers":"4.14.0","Microsoft.CodeAnalysis.Common":"5.0.0","Microsoft.CodeAnalysis.CSharp":"5.0.0","Microsoft.Data.Sqlite":"10.0.3","Microsoft.EntityFrameworkCore.Design":"10.0.3","Microsoft.EntityFrameworkCore.Relational":"10.0.3","Microsoft.EntityFrameworkCore.Sqlite":"10.0.3","Microsoft.EntityFrameworkCore.Tools":"10.0.3","Microsoft.Extensions.Caching.Abstractions":"10.0.3","Microsoft.Extensions.Caching.Memory":"10.0.3","Microsoft.Extensions.Configuration.Abstractions":"10.0.3","Microsoft.Extensions.Configuration.Binder":"10.0.3","Microsoft.Extensions.DependencyInjection":"10.0.3","Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore":"10.0.3","Microsoft.Extensions.Hosting.Abstractions":"10.0.3","Microsoft.Extensions.Http":"10.0.3","Microsoft.Extensions.Logging":"10.0.3","Microsoft.Extensions.Options":"10.0.3","Microsoft.NET.Test.Sdk":"18.0.1","MimeTypes":"2.5.2","Moq":"4.18.4","Morestachio":"5.0.1.631","NEbml":"1.1.0.5","Newtonsoft.Json":"13.0.4","PlaylistsNET":"1.4.1","Polly":"8.6.5","prometheus-net":"8.2.1","prometheus-net.AspNetCore":"8.2.1","prometheus-net.DotNetRuntime":"4.4.1","Serilog.AspNetCore":"10.0.0","Serilog.Enrichers.Thread":"4.0.0","Serilog.Expressions":"5.0.0","Serilog.Settings.Configuration":"10.0.0","Serilog.Sinks.Async":"2.1.0","Serilog.Sinks.Console":"6.1.1","Serilog.Sinks.File":"7.0.0","Serilog.Sinks.Graylog":"3.1.1","SerilogAnalyzer":"0.15.0","SharpFuzz":"2.2.0","SkiaSharp":"[3.116.1]","SkiaSharp.HarfBuzz":"[3.116.1]","SkiaSharp.NativeAssets.Linux":"[3.116.1]","SmartAnalyzers.MultithreadingAnalyzer":"1.1.31","StyleCop.Analyzers":"1.2.0-beta.556","Svg.Skia":"3.4.1","Swashbuckle.AspNetCore":"7.3.2","Swashbuckle.AspNetCore.ReDoc":"6.9.0","System.Text.Json":"10.0.3","TagLibSharp":"2.3.0","TMDbLib":"2.3.0","UTF.Unknown":"2.6.0","xunit":"2.9.3","Xunit.Priority":"1.1.6","xunit.runner.visualstudio":"2.8.2","Xunit.SkippableFact":"1.5.61","z440.atl.core":"7.11.0"},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json","packagesToPrune":{"Microsoft.CSharp":"(,4.7.32767]","Microsoft.VisualBasic":"(,10.4.32767]","Microsoft.Win32.Primitives":"(,4.3.32767]","Microsoft.Win32.Registry":"(,5.0.32767]","runtime.any.System.Collections":"(,4.3.32767]","runtime.any.System.Diagnostics.Tools":"(,4.3.32767]","runtime.any.System.Diagnostics.Tracing":"(,4.3.32767]","runtime.any.System.Globalization":"(,4.3.32767]","runtime.any.System.Globalization.Calendars":"(,4.3.32767]","runtime.any.System.IO":"(,4.3.32767]","runtime.any.System.Reflection":"(,4.3.32767]","runtime.any.System.Reflection.Extensions":"(,4.3.32767]","runtime.any.System.Reflection.Primitives":"(,4.3.32767]","runtime.any.System.Resources.ResourceManager":"(,4.3.32767]","runtime.any.System.Runtime":"(,4.3.32767]","runtime.any.System.Runtime.Handles":"(,4.3.32767]","runtime.any.System.Runtime.InteropServices":"(,4.3.32767]","runtime.any.System.Text.Encoding":"(,4.3.32767]","runtime.any.System.Text.Encoding.Extensions":"(,4.3.32767]","runtime.any.System.Threading.Tasks":"(,4.3.32767]","runtime.any.System.Threading.Timer":"(,4.3.32767]","runtime.aot.System.Collections":"(,4.3.32767]","runtime.aot.System.Diagnostics.Tools":"(,4.3.32767]","runtime.aot.System.Diagnostics.Tracing":"(,4.3.32767]","runtime.aot.System.Globalization":"(,4.3.32767]","runtime.aot.System.Globalization.Calendars":"(,4.3.32767]","runtime.aot.System.IO":"(,4.3.32767]","runtime.aot.System.Reflection":"(,4.3.32767]","runtime.aot.System.Reflection.Extensions":"(,4.3.32767]","runtime.aot.System.Reflection.Primitives":"(,4.3.32767]","runtime.aot.System.Resources.ResourceManager":"(,4.3.32767]","runtime.aot.System.Runtime":"(,4.3.32767]","runtime.aot.System.Runtime.Handles":"(,4.3.32767]","runtime.aot.System.Runtime.InteropServices":"(,4.3.32767]","runtime.aot.System.Text.Encoding":"(,4.3.32767]","runtime.aot.System.Text.Encoding.Extensions":"(,4.3.32767]","runtime.aot.System.Threading.Tasks":"(,4.3.32767]","runtime.aot.System.Threading.Timer":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.unix.Microsoft.Win32.Primitives":"(,4.3.32767]","runtime.unix.System.Console":"(,4.3.32767]","runtime.unix.System.Diagnostics.Debug":"(,4.3.32767]","runtime.unix.System.IO.FileSystem":"(,4.3.32767]","runtime.unix.System.Net.Primitives":"(,4.3.32767]","runtime.unix.System.Net.Sockets":"(,4.3.32767]","runtime.unix.System.Private.Uri":"(,4.3.32767]","runtime.unix.System.Runtime.Extensions":"(,4.3.32767]","runtime.win.Microsoft.Win32.Primitives":"(,4.3.32767]","runtime.win.System.Console":"(,4.3.32767]","runtime.win.System.Diagnostics.Debug":"(,4.3.32767]","runtime.win.System.IO.FileSystem":"(,4.3.32767]","runtime.win.System.Net.Primitives":"(,4.3.32767]","runtime.win.System.Net.Sockets":"(,4.3.32767]","runtime.win.System.Runtime.Extensions":"(,4.3.32767]","runtime.win10-arm-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win10-arm64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win10-x64-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win10-x86-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win7-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win7-x86.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win7.System.Private.Uri":"(,4.3.32767]","runtime.win8-arm.runtime.native.System.IO.Compression":"(,4.3.32767]","System.AppContext":"(,4.3.32767]","System.Buffers":"(,5.0.32767]","System.Collections":"(,4.3.32767]","System.Collections.Concurrent":"(,4.3.32767]","System.Collections.Immutable":"(,10.0.32767]","System.Collections.NonGeneric":"(,4.3.32767]","System.Collections.Specialized":"(,4.3.32767]","System.ComponentModel":"(,4.3.32767]","System.ComponentModel.Annotations":"(,4.3.32767]","System.ComponentModel.EventBasedAsync":"(,4.3.32767]","System.ComponentModel.Primitives":"(,4.3.32767]","System.ComponentModel.TypeConverter":"(,4.3.32767]","System.Console":"(,4.3.32767]","System.Data.Common":"(,4.3.32767]","System.Data.DataSetExtensions":"(,4.4.32767]","System.Diagnostics.Contracts":"(,4.3.32767]","System.Diagnostics.Debug":"(,4.3.32767]","System.Diagnostics.DiagnosticSource":"(,10.0.32767]","System.Diagnostics.FileVersionInfo":"(,4.3.32767]","System.Diagnostics.Process":"(,4.3.32767]","System.Diagnostics.StackTrace":"(,4.3.32767]","System.Diagnostics.TextWriterTraceListener":"(,4.3.32767]","System.Diagnostics.Tools":"(,4.3.32767]","System.Diagnostics.TraceSource":"(,4.3.32767]","System.Diagnostics.Tracing":"(,4.3.32767]","System.Drawing.Primitives":"(,4.3.32767]","System.Dynamic.Runtime":"(,4.3.32767]","System.Formats.Asn1":"(,10.0.32767]","System.Formats.Tar":"(,10.0.32767]","System.Globalization":"(,4.3.32767]","System.Globalization.Calendars":"(,4.3.32767]","System.Globalization.Extensions":"(,4.3.32767]","System.IO":"(,4.3.32767]","System.IO.Compression":"(,4.3.32767]","System.IO.Compression.ZipFile":"(,4.3.32767]","System.IO.FileSystem":"(,4.3.32767]","System.IO.FileSystem.AccessControl":"(,4.4.32767]","System.IO.FileSystem.DriveInfo":"(,4.3.32767]","System.IO.FileSystem.Primitives":"(,4.3.32767]","System.IO.FileSystem.Watcher":"(,4.3.32767]","System.IO.IsolatedStorage":"(,4.3.32767]","System.IO.MemoryMappedFiles":"(,4.3.32767]","System.IO.Pipelines":"(,10.0.32767]","System.IO.Pipes":"(,4.3.32767]","System.IO.Pipes.AccessControl":"(,5.0.32767]","System.IO.UnmanagedMemoryStream":"(,4.3.32767]","System.Linq":"(,4.3.32767]","System.Linq.AsyncEnumerable":"(,10.0.32767]","System.Linq.Expressions":"(,4.3.32767]","System.Linq.Parallel":"(,4.3.32767]","System.Linq.Queryable":"(,4.3.32767]","System.Memory":"(,5.0.32767]","System.Net.Http":"(,4.3.32767]","System.Net.Http.Json":"(,10.0.32767]","System.Net.NameResolution":"(,4.3.32767]","System.Net.NetworkInformation":"(,4.3.32767]","System.Net.Ping":"(,4.3.32767]","System.Net.Primitives":"(,4.3.32767]","System.Net.Requests":"(,4.3.32767]","System.Net.Security":"(,4.3.32767]","System.Net.ServerSentEvents":"(,10.0.32767]","System.Net.Sockets":"(,4.3.32767]","System.Net.WebHeaderCollection":"(,4.3.32767]","System.Net.WebSockets":"(,4.3.32767]","System.Net.WebSockets.Client":"(,4.3.32767]","System.Numerics.Vectors":"(,5.0.32767]","System.ObjectModel":"(,4.3.32767]","System.Private.DataContractSerialization":"(,4.3.32767]","System.Private.Uri":"(,4.3.32767]","System.Reflection":"(,4.3.32767]","System.Reflection.DispatchProxy":"(,6.0.32767]","System.Reflection.Emit":"(,4.7.32767]","System.Reflection.Emit.ILGeneration":"(,4.7.32767]","System.Reflection.Emit.Lightweight":"(,4.7.32767]","System.Reflection.Extensions":"(,4.3.32767]","System.Reflection.Metadata":"(,10.0.32767]","System.Reflection.Primitives":"(,4.3.32767]","System.Reflection.TypeExtensions":"(,4.3.32767]","System.Resources.Reader":"(,4.3.32767]","System.Resources.ResourceManager":"(,4.3.32767]","System.Resources.Writer":"(,4.3.32767]","System.Runtime":"(,4.3.32767]","System.Runtime.CompilerServices.Unsafe":"(,7.0.32767]","System.Runtime.CompilerServices.VisualC":"(,4.3.32767]","System.Runtime.Extensions":"(,4.3.32767]","System.Runtime.Handles":"(,4.3.32767]","System.Runtime.InteropServices":"(,4.3.32767]","System.Runtime.InteropServices.RuntimeInformation":"(,4.3.32767]","System.Runtime.Loader":"(,4.3.32767]","System.Runtime.Numerics":"(,4.3.32767]","System.Runtime.Serialization.Formatters":"(,4.3.32767]","System.Runtime.Serialization.Json":"(,4.3.32767]","System.Runtime.Serialization.Primitives":"(,4.3.32767]","System.Runtime.Serialization.Xml":"(,4.3.32767]","System.Security.AccessControl":"(,6.0.32767]","System.Security.Claims":"(,4.3.32767]","System.Security.Cryptography.Algorithms":"(,4.3.32767]","System.Security.Cryptography.Cng":"(,5.0.32767]","System.Security.Cryptography.Csp":"(,4.3.32767]","System.Security.Cryptography.Encoding":"(,4.3.32767]","System.Security.Cryptography.OpenSsl":"(,5.0.32767]","System.Security.Cryptography.Primitives":"(,4.3.32767]","System.Security.Cryptography.X509Certificates":"(,4.3.32767]","System.Security.Principal":"(,4.3.32767]","System.Security.Principal.Windows":"(,5.0.32767]","System.Security.SecureString":"(,4.3.32767]","System.Text.Encoding":"(,4.3.32767]","System.Text.Encoding.CodePages":"(,10.0.32767]","System.Text.Encoding.Extensions":"(,4.3.32767]","System.Text.Encodings.Web":"(,10.0.32767]","System.Text.Json":"(,10.0.32767]","System.Text.RegularExpressions":"(,4.3.32767]","System.Threading":"(,4.3.32767]","System.Threading.AccessControl":"(,10.0.32767]","System.Threading.Channels":"(,10.0.32767]","System.Threading.Overlapped":"(,4.3.32767]","System.Threading.Tasks":"(,4.3.32767]","System.Threading.Tasks.Dataflow":"(,10.0.32767]","System.Threading.Tasks.Extensions":"(,5.0.32767]","System.Threading.Tasks.Parallel":"(,4.3.32767]","System.Threading.Thread":"(,4.3.32767]","System.Threading.ThreadPool":"(,4.3.32767]","System.Threading.Timer":"(,4.3.32767]","System.ValueTuple":"(,4.5.32767]","System.Xml.ReaderWriter":"(,4.3.32767]","System.Xml.XDocument":"(,4.3.32767]","System.Xml.XmlDocument":"(,4.3.32767]","System.Xml.XmlSerializer":"(,4.3.32767]","System.Xml.XPath":"(,4.3.32767]","System.Xml.XPath.XDocument":"(,5.0.32767]"}}} \ No newline at end of file diff --git a/Emby.Photos/obj/rider.project.model.nuget.info b/Emby.Photos/obj/rider.project.model.nuget.info new file mode 100644 index 00000000..1989e67a --- /dev/null +++ b/Emby.Photos/obj/rider.project.model.nuget.info @@ -0,0 +1 @@ +17714532053700000 \ No newline at end of file diff --git a/Emby.Photos/obj/rider.project.restore.info b/Emby.Photos/obj/rider.project.restore.info new file mode 100644 index 00000000..0a24497c --- /dev/null +++ b/Emby.Photos/obj/rider.project.restore.info @@ -0,0 +1 @@ +17715044201300000 \ No newline at end of file diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs new file mode 100644 index 00000000..de722332 --- /dev/null +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Jellyfin.Extensions; +using MediaBrowser.Common.Configuration; + +namespace Emby.Server.Implementations.AppBase +{ + /// <summary> + /// Provides a base class to hold common application paths used by both the UI and Server. + /// This can be subclassed to add application-specific paths. + /// </summary> + public abstract class BaseApplicationPaths : IApplicationPaths + { + /// <summary> + /// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class. + /// </summary> + /// <param name="programDataPath">The program data path.</param> + /// <param name="logDirectoryPath">The log directory path.</param> + /// <param name="configurationDirectoryPath">The configuration directory path.</param> + /// <param name="cacheDirectoryPath">The cache directory path.</param> + /// <param name="webDirectoryPath">The web directory path.</param> + protected BaseApplicationPaths( + string programDataPath, + string logDirectoryPath, + string configurationDirectoryPath, + string cacheDirectoryPath, + string webDirectoryPath) + { + ProgramDataPath = programDataPath; + LogDirectoryPath = logDirectoryPath; + ConfigurationDirectoryPath = configurationDirectoryPath; + CachePath = cacheDirectoryPath; + WebPath = webDirectoryPath; + DataPath = Directory.CreateDirectory(Path.Combine(ProgramDataPath, "data")).FullName; + } + + /// <inheritdoc/> + public string ProgramDataPath { get; } + + /// <inheritdoc/> + public string WebPath { get; } + + /// <inheritdoc/> + public string ProgramSystemPath { get; } = AppContext.BaseDirectory; + + /// <inheritdoc/> + public string DataPath { get; } + + /// <inheritdoc /> + public string VirtualDataPath => "%AppDataPath%"; + + /// <inheritdoc/> + public string ImageCachePath => Path.Combine(CachePath, "images"); + + /// <inheritdoc/> + public string PluginsPath => Path.Combine(ProgramDataPath, "plugins"); + + /// <inheritdoc/> + public string PluginConfigurationsPath => Path.Combine(PluginsPath, "configurations"); + + /// <inheritdoc/> + public string LogDirectoryPath { get; } + + /// <inheritdoc/> + public string ConfigurationDirectoryPath { get; } + + /// <inheritdoc/> + public string SystemConfigurationFilePath => Path.Combine(ConfigurationDirectoryPath, "system.xml"); + + /// <inheritdoc/> + public string CachePath { get; set; } + + /// <inheritdoc/> + public string TempDirectory => Path.Join(Path.GetTempPath(), "jellyfin"); + + /// <inheritdoc /> + public string TrickplayPath => Path.Combine(DataPath, "trickplay"); + + /// <inheritdoc /> + public string BackupPath => Path.Combine(DataPath, "backups"); + + /// <inheritdoc /> + public virtual void MakeSanityCheckOrThrow() + { + CreateAndCheckMarker(ConfigurationDirectoryPath, "config"); + CreateAndCheckMarker(LogDirectoryPath, "log"); + CreateAndCheckMarker(PluginsPath, "plugin"); + CreateAndCheckMarker(ProgramDataPath, "data"); + CreateAndCheckMarker(CachePath, "cache"); + CreateAndCheckMarker(DataPath, "data"); + } + + /// <inheritdoc /> + public void CreateAndCheckMarker(string path, string markerName, bool recursive = false) + { + Directory.CreateDirectory(path); + + CheckOrCreateMarker(path, $".jellyfin-{markerName}", recursive); + } + + private IEnumerable<string> GetMarkers(string path, bool recursive = false) + { + return Directory.EnumerateFiles(path, ".jellyfin-*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly); + } + + private void CheckOrCreateMarker(string path, string markerName, bool recursive = false) + { + string? otherMarkers = null; + try + { + otherMarkers = GetMarkers(path, recursive).FirstOrDefault(e => !Path.GetFileName(e.AsSpan()).Equals(markerName, StringComparison.OrdinalIgnoreCase)); + } + catch + { + // Error while checking for marker files, assume none exist and keep going + // TODO: add some logging + } + + if (otherMarkers is not null) + { + throw new InvalidOperationException($"Expected to find only {markerName} but found marker for {otherMarkers}."); + } + + var markerPath = Path.Combine(path, markerName); + if (!File.Exists(markerPath)) + { + FileHelper.CreateEmpty(markerPath); + } + } + } +} diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs new file mode 100644 index 00000000..81ef0e5f --- /dev/null +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -0,0 +1,383 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Events; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.AppBase +{ + /// <summary> + /// Class BaseConfigurationManager. + /// </summary> + public abstract class BaseConfigurationManager : IConfigurationManager + { + private readonly ConcurrentDictionary<string, object> _configurations = new(); + private readonly Lock _configurationSyncLock = new(); + + private ConfigurationStore[] _configurationStores = Array.Empty<ConfigurationStore>(); + private IConfigurationFactory[] _configurationFactories = Array.Empty<IConfigurationFactory>(); + + /// <summary> + /// The _configuration. + /// </summary> + private BaseApplicationConfiguration? _configuration; + + /// <summary> + /// Initializes a new instance of the <see cref="BaseConfigurationManager" /> class. + /// </summary> + /// <param name="applicationPaths">The application paths.</param> + /// <param name="loggerFactory">The logger factory.</param> + /// <param name="xmlSerializer">The XML serializer.</param> + protected BaseConfigurationManager( + IApplicationPaths applicationPaths, + ILoggerFactory loggerFactory, + IXmlSerializer xmlSerializer) + { + CommonApplicationPaths = applicationPaths; + XmlSerializer = xmlSerializer; + Logger = loggerFactory.CreateLogger<BaseConfigurationManager>(); + + UpdateCachePath(); + } + + /// <summary> + /// Occurs when [configuration updated]. + /// </summary> + public event EventHandler<EventArgs>? ConfigurationUpdated; + + /// <summary> + /// Occurs when [configuration updating]. + /// </summary> + public event EventHandler<ConfigurationUpdateEventArgs>? NamedConfigurationUpdating; + + /// <summary> + /// Occurs when [named configuration updated]. + /// </summary> + public event EventHandler<ConfigurationUpdateEventArgs>? NamedConfigurationUpdated; + + /// <summary> + /// Gets the type of the configuration. + /// </summary> + /// <value>The type of the configuration.</value> + protected abstract Type ConfigurationType { get; } + + /// <summary> + /// Gets the logger. + /// </summary> + /// <value>The logger.</value> + protected ILogger<BaseConfigurationManager> Logger { get; private set; } + + /// <summary> + /// Gets the XML serializer. + /// </summary> + /// <value>The XML serializer.</value> + protected IXmlSerializer XmlSerializer { get; private set; } + + /// <summary> + /// Gets the application paths. + /// </summary> + /// <value>The application paths.</value> + public IApplicationPaths CommonApplicationPaths { get; private set; } + + /// <summary> + /// Gets or sets the system configuration. + /// </summary> + /// <value>The configuration.</value> + public BaseApplicationConfiguration CommonConfiguration + { + get + { + if (_configuration is not null) + { + return _configuration; + } + + lock (_configurationSyncLock) + { + if (_configuration is not null) + { + return _configuration; + } + + return _configuration = (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer); + } + } + + protected set + { + _configuration = value; + } + } + + /// <summary> + /// Manually pre-loads a factory so that it is available pre system initialisation. + /// </summary> + /// <typeparam name="T">Class to register.</typeparam> + public virtual void RegisterConfiguration<T>() + where T : IConfigurationFactory + { + IConfigurationFactory factory = Activator.CreateInstance<T>(); + + if (_configurationFactories is null) + { + _configurationFactories = [factory]; + } + else + { + _configurationFactories = [.._configurationFactories, factory]; + } + + _configurationStores = _configurationFactories + .SelectMany(i => i.GetConfigurations()) + .ToArray(); + } + + /// <summary> + /// Adds parts. + /// </summary> + /// <param name="factories">The configuration factories.</param> + public virtual void AddParts(IEnumerable<IConfigurationFactory> factories) + { + _configurationFactories = factories.ToArray(); + + _configurationStores = _configurationFactories + .SelectMany(i => i.GetConfigurations()) + .ToArray(); + } + + /// <summary> + /// Saves the configuration. + /// </summary> + public void SaveConfiguration() + { + Logger.LogInformation("Saving system configuration"); + var path = CommonApplicationPaths.SystemConfigurationFilePath; + + Directory.CreateDirectory(Path.GetDirectoryName(path) ?? throw new InvalidOperationException("Path can't be a root directory.")); + + lock (_configurationSyncLock) + { + XmlSerializer.SerializeToFile(CommonConfiguration, path); + } + + OnConfigurationUpdated(); + } + + /// <summary> + /// Called when [configuration updated]. + /// </summary> + protected virtual void OnConfigurationUpdated() + { + UpdateCachePath(); + + EventHelper.QueueEventIfNotNull(ConfigurationUpdated, this, EventArgs.Empty, Logger); + } + + /// <summary> + /// Replaces the configuration. + /// </summary> + /// <param name="newConfiguration">The new configuration.</param> + /// <exception cref="ArgumentNullException"><c>newConfiguration</c> is <c>null</c>.</exception> + public virtual void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration) + { + ArgumentNullException.ThrowIfNull(newConfiguration); + + ValidateCachePath(newConfiguration); + + CommonConfiguration = newConfiguration; + SaveConfiguration(); + } + + /// <summary> + /// Updates the items by name path. + /// </summary> + private void UpdateCachePath() + { + string cachePath; + + // If the configuration file has no entry (i.e. not set in UI) + if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath)) + { + // If the current live configuration has no entry (i.e. not set on CLI/envvars, during startup) + if (string.IsNullOrWhiteSpace(((BaseApplicationPaths)CommonApplicationPaths).CachePath)) + { + // Set cachePath to a default value under ProgramDataPath + cachePath = Path.Combine(((BaseApplicationPaths)CommonApplicationPaths).ProgramDataPath, "cache"); + } + else + { + // Set cachePath to the existing live value; will require restart if UI value is removed (but not replaced) + // TODO: Figure out how to re-grab this from the CLI/envvars while running + cachePath = ((BaseApplicationPaths)CommonApplicationPaths).CachePath; + } + } + else + { + // Set cachePath to the new UI-set value + cachePath = CommonConfiguration.CachePath; + } + + Logger.LogInformation("Setting cache path: {Path}", cachePath); + ((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath; + CommonApplicationPaths.CreateAndCheckMarker(((BaseApplicationPaths)CommonApplicationPaths).CachePath, "cache"); + } + + /// <summary> + /// Replaces the cache path. + /// </summary> + /// <param name="newConfig">The new configuration.</param> + /// <exception cref="DirectoryNotFoundException">The new cache path doesn't exist.</exception> + private void ValidateCachePath(BaseApplicationConfiguration newConfig) + { + var newPath = newConfig.CachePath; + + if (!string.IsNullOrWhiteSpace(newPath) + && !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath, StringComparison.Ordinal)) + { + // Validate + if (!Directory.Exists(newPath)) + { + throw new DirectoryNotFoundException( + string.Format( + CultureInfo.InvariantCulture, + "{0} does not exist.", + newPath)); + } + + EnsureWriteAccess(newPath); + } + } + + /// <summary> + /// Ensures that we have write access to the path. + /// </summary> + /// <param name="path">The path.</param> + protected void EnsureWriteAccess(string path) + { + var file = Path.Combine(path, Guid.NewGuid().ToString()); + File.WriteAllText(file, string.Empty); + File.Delete(file); + } + + private string GetConfigurationFile(string key) + { + return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLowerInvariant() + ".xml"); + } + + /// <inheritdoc /> + public object GetConfiguration(string key) + { + return _configurations.GetOrAdd( + key, + static (k, configurationManager) => + { + var file = configurationManager.GetConfigurationFile(k); + + var configurationInfo = Array.Find( + configurationManager._configurationStores, + i => string.Equals(i.Key, k, StringComparison.OrdinalIgnoreCase)); + + if (configurationInfo is null) + { + throw new ResourceNotFoundException("Configuration with key " + k + " not found."); + } + + var configurationType = configurationInfo.ConfigurationType; + + lock (configurationManager._configurationSyncLock) + { + return configurationManager.LoadConfiguration(file, configurationType); + } + }, + this); + } + + private object LoadConfiguration(string path, Type configurationType) + { + try + { + if (File.Exists(path)) + { + return XmlSerializer.DeserializeFromFile(configurationType, path); + } + } + catch (Exception ex) when (ex is not IOException) + { + Logger.LogError(ex, "Error loading configuration file: {Path}", path); + } + + return Activator.CreateInstance(configurationType) + ?? throw new InvalidOperationException("Configuration type can't be Nullable<T>."); + } + + /// <inheritdoc /> + public void SaveConfiguration(string key, object configuration) + { + var configurationStore = GetConfigurationStore(key); + var configurationType = configurationStore.ConfigurationType; + + if (configuration.GetType() != configurationType) + { + throw new ArgumentException("Expected configuration type is " + configurationType.Name); + } + + if (configurationStore is IValidatingConfiguration validatingStore) + { + var currentConfiguration = GetConfiguration(key); + + validatingStore.Validate(currentConfiguration, configuration); + } + + NamedConfigurationUpdating?.Invoke(this, new ConfigurationUpdateEventArgs(key, configuration)); + + _configurations.AddOrUpdate(key, configuration, (_, _) => configuration); + + var path = GetConfigurationFile(key); + Directory.CreateDirectory(Path.GetDirectoryName(path) ?? throw new InvalidOperationException("Path can't be a root directory.")); + + lock (_configurationSyncLock) + { + XmlSerializer.SerializeToFile(configuration, path); + } + + OnNamedConfigurationUpdated(key, configuration); + } + + /// <summary> + /// Event handler for when a named configuration has been updated. + /// </summary> + /// <param name="key">The key of the configuration.</param> + /// <param name="configuration">The old configuration.</param> + protected virtual void OnNamedConfigurationUpdated(string key, object configuration) + { + NamedConfigurationUpdated?.Invoke(this, new ConfigurationUpdateEventArgs(key, configuration)); + } + + /// <inheritdoc /> + public ConfigurationStore[] GetConfigurationStores() + { + return _configurationStores; + } + + /// <inheritdoc /> + public Type GetConfigurationType(string key) + { + return GetConfigurationStore(key) + .ConfigurationType; + } + + private ConfigurationStore GetConfigurationStore(string key) + { + return _configurationStores + .First(i => string.Equals(i.Key, key, StringComparison.OrdinalIgnoreCase)); + } + } +} diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs new file mode 100644 index 00000000..1c847760 --- /dev/null +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -0,0 +1,62 @@ +using System; +using System.IO; +using MediaBrowser.Model.Serialization; + +namespace Emby.Server.Implementations.AppBase +{ + /// <summary> + /// Class ConfigurationHelper. + /// </summary> + public static class ConfigurationHelper + { + /// <summary> + /// Reads an xml configuration file from the file system + /// It will immediately re-serialize and save if new serialization data is available due to property changes. + /// </summary> + /// <param name="type">The type.</param> + /// <param name="path">The path.</param> + /// <param name="xmlSerializer">The XML serializer.</param> + /// <returns>System.Object.</returns> + public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer) + { + object configuration; + + byte[]? buffer = null; + + // Use try/catch to avoid the extra file system lookup using File.Exists + try + { + buffer = File.ReadAllBytes(path); + + configuration = xmlSerializer.DeserializeFromBytes(type, buffer); + } + catch (Exception) + { + // Note: CreateInstance returns null for Nullable<T>, e.g. CreateInstance(typeof(int?)) returns null. + configuration = Activator.CreateInstance(type)!; + } + + using var stream = new MemoryStream(buffer?.Length ?? 0); + xmlSerializer.SerializeToStream(configuration, stream); + + // Take the object we just got and serialize it back to bytes + Span<byte> newBytes = stream.GetBuffer().AsSpan(0, (int)stream.Length); + + // If the file didn't exist before, or if something has changed, re-save + if (buffer is null || !newBytes.SequenceEqual(buffer)) + { + var directory = Path.GetDirectoryName(path) ?? throw new ArgumentException($"Provided path ({path}) is not valid.", nameof(path)); + + Directory.CreateDirectory(directory); + + // Save it after load in case we got new items + using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None)) + { + fs.Write(newBytes); + } + } + + return configuration; + } + } +} diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs new file mode 100644 index 00000000..cbb0f6c5 --- /dev/null +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -0,0 +1,1014 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using Emby.Naming.Common; +using Emby.Photos; +using Emby.Server.Implementations.Chapters; +using Emby.Server.Implementations.Collections; +using Emby.Server.Implementations.Configuration; +using Emby.Server.Implementations.Cryptography; +using Emby.Server.Implementations.Data; +using Emby.Server.Implementations.Devices; +using Emby.Server.Implementations.Dto; +using Emby.Server.Implementations.HttpServer.Security; +using Emby.Server.Implementations.IO; +using Emby.Server.Implementations.Library; +using Emby.Server.Implementations.Localization; +using Emby.Server.Implementations.Playlists; +using Emby.Server.Implementations.Plugins; +using Emby.Server.Implementations.QuickConnect; +using Emby.Server.Implementations.ScheduledTasks; +using Emby.Server.Implementations.Serialization; +using Emby.Server.Implementations.Session; +using Emby.Server.Implementations.SyncPlay; +using Emby.Server.Implementations.TV; +using Emby.Server.Implementations.Updates; +using Jellyfin.Api.Helpers; +using Jellyfin.Drawing; +using Jellyfin.MediaEncoding.Hls.Playlist; +using Jellyfin.Networking.Manager; +using Jellyfin.Networking.Udp; +using Jellyfin.Server.Implementations.FullSystemBackup; +using Jellyfin.Server.Implementations.Item; +using Jellyfin.Server.Implementations.MediaSegments; +using Jellyfin.Server.Implementations.SystemBackupService; +using MediaBrowser.Common; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Events; +using MediaBrowser.Common.Net; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Chapters; +using MediaBrowser.Controller.ClientEvent; +using MediaBrowser.Controller.Collections; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LibraryTaskScheduler; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Lyrics; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.MediaSegments; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Playlists; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.QuickConnect; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Controller.SyncPlay; +using MediaBrowser.Controller.TV; +using MediaBrowser.LocalMetadata.Savers; +using MediaBrowser.MediaEncoding.BdInfo; +using MediaBrowser.MediaEncoding.Subtitles; +using MediaBrowser.MediaEncoding.Transcoding; +using MediaBrowser.Model.Cryptography; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.System; +using MediaBrowser.Model.Tasks; +using MediaBrowser.Providers.Lyric; +using MediaBrowser.Providers.Manager; +using MediaBrowser.Providers.Plugins.Tmdb; +using MediaBrowser.Providers.Subtitles; +using MediaBrowser.XbmcMetadata.Providers; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Prometheus.DotNetRuntime; +using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; +using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager; +using WebSocketManager = Emby.Server.Implementations.HttpServer.WebSocketManager; + +namespace Emby.Server.Implementations +{ + /// <summary> + /// Class CompositionRoot. + /// </summary> + public abstract class ApplicationHost : IServerApplicationHost, IDisposable + { + /// <summary> + /// The disposable parts. + /// </summary> + private readonly ConcurrentBag<IDisposable> _disposableParts = new(); + private readonly DeviceId _deviceId; + + private readonly IConfiguration _startupConfig; + private readonly IXmlSerializer _xmlSerializer; + private readonly IStartupOptions _startupOptions; + private readonly PluginManager _pluginManager; + + private List<Type> _creatingInstances; + + /// <summary> + /// Gets or sets all concrete types. + /// </summary> + /// <value>All concrete types.</value> + private Type[] _allConcreteTypes; + + private bool _disposed; + + /// <summary> + /// Initializes a new instance of the <see cref="ApplicationHost"/> class. + /// </summary> + /// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param> + /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param> + /// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param> + /// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param> + protected ApplicationHost( + IServerApplicationPaths applicationPaths, + ILoggerFactory loggerFactory, + IStartupOptions options, + IConfiguration startupConfig) + { + ApplicationPaths = applicationPaths; + LoggerFactory = loggerFactory; + _startupOptions = options; + _startupConfig = startupConfig; + + Logger = LoggerFactory.CreateLogger<ApplicationHost>(); + _deviceId = new DeviceId(ApplicationPaths, LoggerFactory.CreateLogger<DeviceId>()); + + ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version; + ApplicationVersionString = ApplicationVersion.ToString(3); + ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString; + + _xmlSerializer = new MyXmlSerializer(); + ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer); + _pluginManager = new PluginManager( + LoggerFactory.CreateLogger<PluginManager>(), + this, + ConfigurationManager.Configuration, + ApplicationPaths.PluginsPath, + ApplicationVersion); + + _disposableParts.Add(_pluginManager); + } + + /// <summary> + /// Occurs when [has pending restart changed]. + /// </summary> + public event EventHandler HasPendingRestartChanged; + + /// <summary> + /// Gets the value of the PublishedServerUrl setting. + /// </summary> + private string PublishedServerUrl => _startupConfig[AddressOverrideKey]; + + public bool CoreStartupHasCompleted { get; private set; } + + /// <summary> + /// Gets the <see cref="INetworkManager"/> singleton instance. + /// </summary> + public INetworkManager NetManager { get; private set; } + + /// <inheritdoc /> + public bool HasPendingRestart { get; private set; } + + /// <inheritdoc /> + public bool ShouldRestart { get; set; } + + /// <summary> + /// Gets the logger. + /// </summary> + protected ILogger<ApplicationHost> Logger { get; } + + /// <summary> + /// Gets the logger factory. + /// </summary> + protected ILoggerFactory LoggerFactory { get; } + + /// <summary> + /// Gets the application paths. + /// </summary> + /// <value>The application paths.</value> + protected IServerApplicationPaths ApplicationPaths { get; } + + /// <summary> + /// Gets the configuration manager. + /// </summary> + /// <value>The configuration manager.</value> + public ServerConfigurationManager ConfigurationManager { get; } + + /// <summary> + /// Gets or sets the service provider. + /// </summary> + public IServiceProvider ServiceProvider { get; set; } + + /// <summary> + /// Gets the http port for the webhost. + /// </summary> + public int HttpPort { get; private set; } + + /// <summary> + /// Gets the https port for the webhost. + /// </summary> + public int HttpsPort { get; private set; } + + /// <inheritdoc /> + public Version ApplicationVersion { get; } + + /// <inheritdoc /> + public string ApplicationVersionString { get; } + + /// <summary> + /// Gets the current application user agent. + /// </summary> + /// <value>The application user agent.</value> + public string ApplicationUserAgent { get; } + + /// <summary> + /// Gets the email address for use within a comment section of a user agent field. + /// Presently used to provide contact information to MusicBrainz service. + /// </summary> + public string ApplicationUserAgentAddress => "team@jellyfin.org"; + + /// <summary> + /// Gets the current application name. + /// </summary> + /// <value>The application name.</value> + public string ApplicationProductName { get; } = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName; + + public string SystemId => _deviceId.Value; + + /// <inheritdoc/> + public string Name => ApplicationProductName; + + private string CertificatePath { get; set; } + + public X509Certificate2 Certificate { get; private set; } + + /// <inheritdoc/> + public bool ListenWithHttps => Certificate is not null && ConfigurationManager.GetNetworkConfiguration().EnableHttps; + + public string FriendlyName => + string.IsNullOrEmpty(ConfigurationManager.Configuration.ServerName) + ? Environment.MachineName + : ConfigurationManager.Configuration.ServerName; + + public string RestoreBackupPath { get; set; } + + public string ExpandVirtualPath(string path) + { + if (path is null) + { + return null; + } + + var appPaths = ApplicationPaths; + + return path.Replace(appPaths.VirtualDataPath, appPaths.DataPath, StringComparison.OrdinalIgnoreCase) + .Replace(appPaths.VirtualInternalMetadataPath, appPaths.InternalMetadataPath, StringComparison.OrdinalIgnoreCase); + } + + public string ReverseVirtualPath(string path) + { + var appPaths = ApplicationPaths; + + return path.Replace(appPaths.DataPath, appPaths.VirtualDataPath, StringComparison.OrdinalIgnoreCase) + .Replace(appPaths.InternalMetadataPath, appPaths.VirtualInternalMetadataPath, StringComparison.OrdinalIgnoreCase); + } + + /// <summary> + /// Creates the instance safe. + /// </summary> + /// <param name="type">The type.</param> + /// <returns>System.Object.</returns> + protected object CreateInstanceSafe(Type type) + { + _creatingInstances ??= new List<Type>(); + + if (_creatingInstances.Contains(type)) + { + Logger.LogError("DI Loop detected in the attempted creation of {Type}", type.FullName); + foreach (var entry in _creatingInstances) + { + Logger.LogError("Called from: {TypeName}", entry.FullName); + } + + _pluginManager.FailPlugin(type.Assembly); + + throw new TypeLoadException("DI Loop detected"); + } + + try + { + _creatingInstances.Add(type); + Logger.LogDebug("Creating instance of {Type}", type); + return ServiceProvider is null + ? Activator.CreateInstance(type) + : ActivatorUtilities.CreateInstance(ServiceProvider, type); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error creating {Type}", type); + // If this is a plugin fail it. + _pluginManager.FailPlugin(type.Assembly); + return null; + } + finally + { + _creatingInstances.Remove(type); + } + } + + /// <summary> + /// Resolves this instance. + /// </summary> + /// <typeparam name="T">The type.</typeparam> + /// <returns>``0.</returns> + public T Resolve<T>() => ServiceProvider.GetService<T>(); + + /// <inheritdoc/> + public IEnumerable<Type> GetExportTypes<T>() + { + var currentType = typeof(T); + var numberOfConcreteTypes = _allConcreteTypes.Length; + for (var i = 0; i < numberOfConcreteTypes; i++) + { + var type = _allConcreteTypes[i]; + if (currentType.IsAssignableFrom(type)) + { + yield return type; + } + } + } + + /// <inheritdoc /> + public IReadOnlyCollection<T> GetExports<T>(bool manageLifetime = true) + { + // Convert to list so this isn't executed for each iteration + var parts = GetExportTypes<T>() + .Select(CreateInstanceSafe) + .Where(i => i is not null) + .Cast<T>() + .ToList(); + + if (manageLifetime) + { + foreach (var part in parts.OfType<IDisposable>()) + { + _disposableParts.Add(part); + } + } + + return parts; + } + + /// <inheritdoc /> + public IReadOnlyCollection<T> GetExports<T>(CreationDelegateFactory defaultFunc, bool manageLifetime = true) + { + // Convert to list so this isn't executed for each iteration + var parts = GetExportTypes<T>() + .Select(i => defaultFunc(i)) + .Where(i => i is not null) + .Cast<T>() + .ToList(); + + if (manageLifetime) + { + foreach (var part in parts.OfType<IDisposable>()) + { + _disposableParts.Add(part); + } + } + + return parts; + } + + /// <summary> + /// Runs the startup tasks. + /// </summary> + /// <returns><see cref="Task" />.</returns> + public Task RunStartupTasksAsync() + { + Logger.LogInformation("Running startup tasks"); + + Resolve<ITaskManager>().AddTasks(GetExports<IScheduledTask>(false)); + + ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated; + ConfigurationManager.NamedConfigurationUpdated += OnConfigurationUpdated; + + var ffmpegValid = Resolve<IMediaEncoder>().SetFFmpegPath(); + + if (!ffmpegValid) + { + throw new FfmpegException("Failed to find valid ffmpeg"); + } + + Logger.LogInformation("ServerId: {ServerId}", SystemId); + Logger.LogInformation("Core startup complete"); + CoreStartupHasCompleted = true; + + return Task.CompletedTask; + } + + /// <inheritdoc/> + public void Init(IServiceCollection serviceCollection) + { + DiscoverTypes(); + + ConfigurationManager.AddParts(GetExports<IConfigurationFactory>()); + + NetManager = new NetworkManager(ConfigurationManager, _startupConfig, LoggerFactory.CreateLogger<NetworkManager>()); + + // Initialize runtime stat collection + if (ConfigurationManager.Configuration.EnableMetrics) + { + _disposableParts.Add(DotNetRuntimeStatsBuilder.Default().StartCollecting()); + } + + var networkConfiguration = ConfigurationManager.GetNetworkConfiguration(); + HttpPort = networkConfiguration.InternalHttpPort; + HttpsPort = networkConfiguration.InternalHttpsPort; + + // Safeguard against invalid configuration + if (HttpPort == HttpsPort) + { + HttpPort = NetworkConfiguration.DefaultHttpPort; + HttpsPort = NetworkConfiguration.DefaultHttpsPort; + } + + CertificatePath = networkConfiguration.CertificatePath; + Certificate = GetCertificate(CertificatePath, networkConfiguration.CertificatePassword); + + RegisterServices(serviceCollection); + + _pluginManager.RegisterServices(serviceCollection); + } + + /// <summary> + /// Registers services/resources with the service collection that will be available via DI. + /// </summary> + /// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param> + protected virtual void RegisterServices(IServiceCollection serviceCollection) + { + serviceCollection.AddSingleton(_startupOptions); + + serviceCollection.AddMemoryCache(); + + serviceCollection.AddSingleton<IServerConfigurationManager>(ConfigurationManager); + serviceCollection.AddSingleton<IConfigurationManager>(ConfigurationManager); + serviceCollection.AddSingleton<IApplicationHost>(this); + serviceCollection.AddSingleton<IPluginManager>(_pluginManager); + serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths); + serviceCollection.AddSingleton<IBackupService, BackupService>(); + + serviceCollection.AddSingleton<IFileSystem, ManagedFileSystem>(); + serviceCollection.AddSingleton<IShortcutHandler, MbLinkShortcutHandler>(); + + serviceCollection.AddScoped<ISystemManager, SystemManager>(); + + serviceCollection.AddSingleton<TmdbClientManager>(); + + serviceCollection.AddSingleton(NetManager); + + serviceCollection.AddSingleton<ITaskManager, TaskManager>(); + + serviceCollection.AddSingleton(_xmlSerializer); + + serviceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>(); + + serviceCollection.AddSingleton<ISocketFactory, SocketFactory>(); + + serviceCollection.AddSingleton<IInstallationManager, InstallationManager>(); + + serviceCollection.AddSingleton<IServerApplicationHost>(this); + serviceCollection.AddSingleton(ApplicationPaths); + + serviceCollection.AddSingleton<ILocalizationManager, LocalizationManager>(); + + serviceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>(); + + serviceCollection.AddSingleton<IUserDataManager, UserDataManager>(); + + serviceCollection.AddSingleton<IItemRepository, BaseItemRepository>(); + serviceCollection.AddSingleton<IPeopleRepository, PeopleRepository>(); + serviceCollection.AddSingleton<IChapterRepository, ChapterRepository>(); + serviceCollection.AddSingleton<IMediaAttachmentRepository, MediaAttachmentRepository>(); + serviceCollection.AddSingleton<IMediaStreamRepository, MediaStreamRepository>(); + serviceCollection.AddSingleton<IKeyframeRepository, KeyframeRepository>(); + serviceCollection.AddSingleton<IItemTypeLookup, ItemTypeLookup>(); + + serviceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(); + serviceCollection.AddSingleton<EncodingHelper>(); + serviceCollection.AddSingleton<IPathManager, PathManager>(); + serviceCollection.AddSingleton<IExternalDataManager, ExternalDataManager>(); + + // TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required + serviceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>)); + serviceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>)); + serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>)); + serviceCollection.AddSingleton<ILibraryManager, LibraryManager>(); + serviceCollection.AddSingleton<NamingOptions>(); + + serviceCollection.AddSingleton<IMusicManager, MusicManager>(); + + serviceCollection.AddSingleton<ILibraryMonitor, LibraryMonitor>(); + + serviceCollection.AddSingleton<ISearchEngine, SearchEngine>(); + + serviceCollection.AddSingleton<IWebSocketManager, WebSocketManager>(); + + serviceCollection.AddSingleton<IImageProcessor, ImageProcessor>(); + + serviceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>(); + + serviceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>(); + + serviceCollection.AddSingleton<ISubtitleManager, SubtitleManager>(); + serviceCollection.AddSingleton<ILyricManager, LyricManager>(); + + serviceCollection.AddSingleton<IProviderManager, ProviderManager>(); + + // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required + serviceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>)); + serviceCollection.AddSingleton<IDtoService, DtoService>(); + + serviceCollection.AddSingleton<ISessionManager, SessionManager>(); + + serviceCollection.AddSingleton<ICollectionManager, CollectionManager>(); + serviceCollection.AddSingleton<ILimitedConcurrencyLibraryScheduler, LimitedConcurrencyLibraryScheduler>(); + + serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>(); + + serviceCollection.AddSingleton<ISyncPlayManager, SyncPlayManager>(); + + serviceCollection.AddSingleton<IUserViewManager, UserViewManager>(); + + serviceCollection.AddSingleton<IChapterManager, ChapterManager>(); + + serviceCollection.AddSingleton<IAuthService, AuthService>(); + serviceCollection.AddSingleton<IQuickConnect, QuickConnectManager>(); + + serviceCollection.AddSingleton<ISubtitleParser, SubtitleEditParser>(); + serviceCollection.AddSingleton<ISubtitleEncoder, SubtitleEncoder>(); + serviceCollection.AddSingleton<IKeyframeManager, KeyframeManager>(); + + serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>(); + + serviceCollection.AddSingleton<ITranscodeManager, TranscodeManager>(); + serviceCollection.AddScoped<MediaInfoHelper>(); + serviceCollection.AddScoped<AudioHelper>(); + serviceCollection.AddScoped<DynamicHlsHelper>(); + serviceCollection.AddScoped<IClientEventLogger, ClientEventLogger>(); + serviceCollection.AddSingleton<IDirectoryService, DirectoryService>(); + + serviceCollection.AddSingleton<IMediaSegmentManager, MediaSegmentManager>(); + } + + /// <summary> + /// Create services registered with the service container that need to be initialized at application startup. + /// </summary> + /// <param name="startupConfig">The configuration used to initialise the application.</param> + /// <returns>A task representing the service initialization operation.</returns> + public async Task InitializeServices(IConfiguration startupConfig) + { + var localizationManager = (LocalizationManager)Resolve<ILocalizationManager>(); + await localizationManager.LoadAll().ConfigureAwait(false); + + SetStaticProperties(); + + FindParts(); + } + + private X509Certificate2 GetCertificate(string path, string password) + { + if (string.IsNullOrWhiteSpace(path)) + { + return null; + } + + try + { + if (!File.Exists(path)) + { + return null; + } + + // Don't use an empty string password + password = string.IsNullOrWhiteSpace(password) ? null : password; + + var localCert = X509CertificateLoader.LoadPkcs12FromFile(path, password, X509KeyStorageFlags.UserKeySet); + if (!localCert.HasPrivateKey) + { + Logger.LogError("No private key included in SSL cert {CertificateLocation}.", path); + return null; + } + + return localCert; + } + catch (Exception ex) + { + Logger.LogError(ex, "Error loading cert from {CertificateLocation}", path); + return null; + } + } + + /// <summary> + /// Dirty hacks. + /// </summary> + private void SetStaticProperties() + { + // For now there's no real way to inject these properly + BaseItem.ChapterManager = Resolve<IChapterManager>(); + BaseItem.ChannelManager = Resolve<IChannelManager>(); + BaseItem.ConfigurationManager = ConfigurationManager; + BaseItem.FileSystem = Resolve<IFileSystem>(); + BaseItem.ItemRepository = Resolve<IItemRepository>(); + BaseItem.LibraryManager = Resolve<ILibraryManager>(); + BaseItem.LocalizationManager = Resolve<ILocalizationManager>(); + BaseItem.Logger = Resolve<ILogger<BaseItem>>(); + BaseItem.MediaSegmentManager = Resolve<IMediaSegmentManager>(); + BaseItem.MediaSourceManager = Resolve<IMediaSourceManager>(); + BaseItem.ProviderManager = Resolve<IProviderManager>(); + BaseItem.UserDataManager = Resolve<IUserDataManager>(); + CollectionFolder.XmlSerializer = _xmlSerializer; + CollectionFolder.ApplicationHost = this; + Folder.UserViewManager = Resolve<IUserViewManager>(); + Folder.CollectionManager = Resolve<ICollectionManager>(); + Folder.LimitedConcurrencyLibraryScheduler = Resolve<ILimitedConcurrencyLibraryScheduler>(); + Episode.MediaEncoder = Resolve<IMediaEncoder>(); + UserView.TVSeriesManager = Resolve<ITVSeriesManager>(); + Video.RecordingsManager = Resolve<IRecordingsManager>(); + } + + /// <summary> + /// Finds plugin components and register them with the appropriate services. + /// </summary> + private void FindParts() + { + if (!ConfigurationManager.Configuration.IsPortAuthorized) + { + ConfigurationManager.Configuration.IsPortAuthorized = true; + ConfigurationManager.SaveConfiguration(); + } + + _pluginManager.CreatePlugins(); + + Resolve<ILibraryManager>().AddParts( + GetExports<IResolverIgnoreRule>(), + GetExports<IItemResolver>(), + GetExports<IIntroProvider>(), + GetExports<IBaseItemComparer>(), + GetExports<ILibraryPostScanTask>()); + + Resolve<IProviderManager>().AddParts( + GetExports<IImageProvider>(), + GetExports<IMetadataService>(), + GetExports<IMetadataProvider>(), + GetExports<IMetadataSaver>(), + GetExports<IExternalId>(), + GetExports<IExternalUrlProvider>()); + + Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>()); + } + + /// <summary> + /// Discovers the types. + /// </summary> + protected void DiscoverTypes() + { + Logger.LogInformation("Loading assemblies"); + + _allConcreteTypes = GetTypes(GetComposablePartAssemblies()).ToArray(); + } + + private IEnumerable<Type> GetTypes(IEnumerable<Assembly> assemblies) + { + foreach (var ass in assemblies) + { + Type[] exportedTypes; + try + { + exportedTypes = ass.GetExportedTypes(); + } + catch (FileNotFoundException ex) + { + Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName); + _pluginManager.FailPlugin(ass); + continue; + } + catch (TypeLoadException ex) + { + Logger.LogError(ex, "Error loading types from {Assembly}.", ass.FullName); + _pluginManager.FailPlugin(ass); + continue; + } + + foreach (Type type in exportedTypes) + { + if (type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType) + { + yield return type; + } + } + } + } + + /// <summary> + /// Called when [configuration updated]. + /// </summary> + /// <param name="sender">The sender.</param> + /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> + private void OnConfigurationUpdated(object sender, EventArgs e) + { + var requiresRestart = false; + var networkConfiguration = ConfigurationManager.GetNetworkConfiguration(); + + // Don't do anything if these haven't been set yet + if (HttpPort != 0 && HttpsPort != 0) + { + // Need to restart if ports have changed + if (networkConfiguration.InternalHttpPort != HttpPort + || networkConfiguration.InternalHttpsPort != HttpsPort) + { + if (ConfigurationManager.Configuration.IsPortAuthorized) + { + ConfigurationManager.Configuration.IsPortAuthorized = false; + ConfigurationManager.SaveConfiguration(); + + requiresRestart = true; + } + } + } + + if (ValidateSslCertificate(networkConfiguration)) + { + requiresRestart = true; + } + + if (requiresRestart) + { + Logger.LogInformation("App needs to be restarted due to configuration change."); + + NotifyPendingRestart(); + } + } + + /// <summary> + /// Validates the SSL certificate. + /// </summary> + /// <param name="networkConfig">The new configuration.</param> + /// <exception cref="FileNotFoundException">The certificate path doesn't exist.</exception> + private bool ValidateSslCertificate(NetworkConfiguration networkConfig) + { + var newPath = networkConfig.CertificatePath; + + if (!string.IsNullOrWhiteSpace(newPath) + && !string.Equals(CertificatePath, newPath, StringComparison.Ordinal)) + { + if (File.Exists(newPath)) + { + return true; + } + + throw new FileNotFoundException( + string.Format( + CultureInfo.InvariantCulture, + "Certificate file '{0}' does not exist.", + newPath)); + } + + return false; + } + + /// <summary> + /// Notifies the kernel that a change has been made that requires a restart. + /// </summary> + public void NotifyPendingRestart() + { + Logger.LogInformation("App needs to be restarted."); + + var changed = !HasPendingRestart; + + HasPendingRestart = true; + + if (changed) + { + EventHelper.QueueEventIfNotNull(HasPendingRestartChanged, this, EventArgs.Empty, Logger); + } + } + + /// <summary> + /// Gets the composable part assemblies. + /// </summary> + /// <returns>IEnumerable{Assembly}.</returns> + protected IEnumerable<Assembly> GetComposablePartAssemblies() + { + foreach (var p in _pluginManager.LoadAssemblies()) + { + yield return p; + } + + // Include composable parts in the Model assembly + yield return typeof(SystemInfo).Assembly; + + // Include composable parts in the Common assembly + yield return typeof(IApplicationHost).Assembly; + + // Include composable parts in the Controller assembly + yield return typeof(IServerApplicationHost).Assembly; + + // Include composable parts in the Providers assembly + yield return typeof(ProviderManager).Assembly; + + // Include composable parts in the Photos assembly + yield return typeof(PhotoProvider).Assembly; + + // Emby.Server implementations + yield return typeof(InstallationManager).Assembly; + + // MediaEncoding + yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly; + + // Local metadata + yield return typeof(BoxSetXmlSaver).Assembly; + + // Xbmc + yield return typeof(ArtistNfoProvider).Assembly; + + // Network + yield return typeof(NetworkManager).Assembly; + + // Hls + yield return typeof(DynamicHlsPlaylistGenerator).Assembly; + + foreach (var i in GetAssembliesWithPartsInternal()) + { + yield return i; + } + } + + protected abstract IEnumerable<Assembly> GetAssembliesWithPartsInternal(); + + /// <inheritdoc/> + public string GetSmartApiUrl(IPAddress remoteAddr) + { + // Published server ends with a / + if (!string.IsNullOrEmpty(PublishedServerUrl)) + { + // Published server ends with a '/', so we need to remove it. + return PublishedServerUrl.Trim('/'); + } + + string smart = NetManager.GetBindAddress(remoteAddr, out var port); + return GetLocalApiUrl(smart.Trim('/'), null, port); + } + + /// <inheritdoc/> + public string GetSmartApiUrl(HttpRequest request) + { + // Return the host in the HTTP request as the API URL if not configured otherwise + if (ConfigurationManager.GetNetworkConfiguration().EnablePublishedServerUriByRequest) + { + int? requestPort = request.Host.Port; + if (requestPort is null + || (requestPort == 80 && string.Equals(request.Scheme, "http", StringComparison.OrdinalIgnoreCase)) + || (requestPort == 443 && string.Equals(request.Scheme, "https", StringComparison.OrdinalIgnoreCase))) + { + requestPort = -1; + } + + return GetLocalApiUrl(request.Host.Host, request.Scheme, requestPort); + } + + return GetSmartApiUrl(request.HttpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback); + } + + /// <inheritdoc/> + public string GetSmartApiUrl(string hostname) + { + // Published server ends with a / + if (!string.IsNullOrEmpty(PublishedServerUrl)) + { + // Published server ends with a '/', so we need to remove it. + return PublishedServerUrl.Trim('/'); + } + + string smart = NetManager.GetBindAddress(hostname, out var port); + return GetLocalApiUrl(smart.Trim('/'), null, port); + } + + /// <inheritdoc/> + public string GetApiUrlForLocalAccess(IPAddress ipAddress = null, bool allowHttps = true) + { + // With an empty source, the port will be null + var smart = NetManager.GetBindAddress(ipAddress, out _, false); + var scheme = !allowHttps ? Uri.UriSchemeHttp : null; + int? port = !allowHttps ? HttpPort : null; + return GetLocalApiUrl(smart, scheme, port); + } + + /// <inheritdoc/> + public string GetLocalApiUrl(string hostname, string scheme = null, int? port = null) + { + // If the smartAPI doesn't start with http then treat it as a host or ip. + if (hostname.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + { + return hostname.TrimEnd('/'); + } + + // NOTE: If no BaseUrl is set then UriBuilder appends a trailing slash, but if there is no BaseUrl it does + // not. For consistency, always trim the trailing slash. + scheme ??= ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp; + var isHttps = string.Equals(scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase); + return new UriBuilder + { + Scheme = scheme, + Host = hostname, + Port = port ?? (isHttps ? HttpsPort : HttpPort), + Path = ConfigurationManager.GetNetworkConfiguration().BaseUrl + }.ToString().TrimEnd('/'); + } + + public IEnumerable<Assembly> GetApiPluginAssemblies() + { + var assemblies = _allConcreteTypes + .Where(i => typeof(ControllerBase).IsAssignableFrom(i)) + .Select(i => i.Assembly) + .Distinct(); + + foreach (var assembly in assemblies) + { + Logger.LogDebug("Found API endpoints in plugin {Name}", assembly.FullName); + yield return assembly; + } + } + + /// <inheritdoc /> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> + /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool dispose) + { + if (_disposed) + { + return; + } + + if (dispose) + { + var type = GetType(); + + Logger.LogInformation("Disposing {Type}", type.Name); + + foreach (var part in _disposableParts.ToArray()) + { + var partType = part.GetType(); + if (partType == type) + { + continue; + } + + Logger.LogInformation("Disposing {Type}", partType.Name); + + try + { + part.Dispose(); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error disposing {Type}", partType.Name); + } + } + + _disposableParts.Clear(); + } + + _disposed = true; + } + } +} diff --git a/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs b/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs new file mode 100644 index 00000000..7ae26bd8 --- /dev/null +++ b/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Branding; + +namespace Emby.Server.Implementations.Branding +{ + /// <summary> + /// A configuration factory for <see cref="BrandingOptions"/>. + /// </summary> + public class BrandingConfigurationFactory : IConfigurationFactory + { + /// <inheritdoc /> + public IEnumerable<ConfigurationStore> GetConfigurations() + { + return new[] + { + new ConfigurationStore + { + ConfigurationType = typeof(BrandingOptions), + Key = "branding" + } + }; + } + } +} diff --git a/Emby.Server.Implementations/Chapters/ChapterManager.cs b/Emby.Server.Implementations/Chapters/ChapterManager.cs new file mode 100644 index 00000000..d09ed30a --- /dev/null +++ b/Emby.Server.Implementations/Chapters/ChapterManager.cs @@ -0,0 +1,300 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Chapters; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.MediaInfo; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Chapters; + +/// <summary> +/// The chapter manager. +/// </summary> +public class ChapterManager : IChapterManager +{ + private readonly IFileSystem _fileSystem; + private readonly ILogger<ChapterManager> _logger; + private readonly IMediaEncoder _encoder; + private readonly IChapterRepository _chapterRepository; + private readonly ILibraryManager _libraryManager; + private readonly IPathManager _pathManager; + + /// <summary> + /// The first chapter ticks. + /// </summary> + private static readonly long _firstChapterTicks = TimeSpan.FromSeconds(15).Ticks; + + /// <summary> + /// Initializes a new instance of the <see cref="ChapterManager"/> class. + /// </summary> + /// <param name="logger">The <see cref="ILogger{ChapterManager}"/>.</param> + /// <param name="fileSystem">The <see cref="IFileSystem"/>.</param> + /// <param name="encoder">The <see cref="IMediaEncoder"/>.</param> + /// <param name="chapterRepository">The <see cref="IChapterRepository"/>.</param> + /// <param name="libraryManager">The <see cref="ILibraryManager"/>.</param> + /// <param name="pathManager">The <see cref="IPathManager"/>.</param> + public ChapterManager( + ILogger<ChapterManager> logger, + IFileSystem fileSystem, + IMediaEncoder encoder, + IChapterRepository chapterRepository, + ILibraryManager libraryManager, + IPathManager pathManager) + { + _logger = logger; + _fileSystem = fileSystem; + _encoder = encoder; + _chapterRepository = chapterRepository; + _libraryManager = libraryManager; + _pathManager = pathManager; + } + + /// <summary> + /// Determines whether [is eligible for chapter image extraction] [the specified video]. + /// </summary> + /// <param name="video">The video.</param> + /// <param name="libraryOptions">The library options for the video.</param> + /// <returns><c>true</c> if [is eligible for chapter image extraction] [the specified video]; otherwise, <c>false</c>.</returns> + private bool IsEligibleForChapterImageExtraction(Video video, LibraryOptions libraryOptions) + { + if (video.IsPlaceHolder) + { + return false; + } + + if (libraryOptions is null || !libraryOptions.EnableChapterImageExtraction) + { + return false; + } + + if (video.IsShortcut) + { + return false; + } + + if (!video.IsCompleteMedia) + { + return false; + } + + // Can't extract images if there are no video streams + return video.DefaultVideoStreamIndex.HasValue; + } + + private long GetAverageDurationBetweenChapters(IReadOnlyList<ChapterInfo> chapters) + { + if (chapters.Count < 2) + { + return 0; + } + + long sum = 0; + for (int i = 1; i < chapters.Count; i++) + { + sum += chapters[i].StartPositionTicks - chapters[i - 1].StartPositionTicks; + } + + return sum / chapters.Count; + } + + /// <inheritdoc /> + public async Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken) + { + if (chapters.Count == 0) + { + return true; + } + + var libraryOptions = _libraryManager.GetLibraryOptions(video); + + if (!IsEligibleForChapterImageExtraction(video, libraryOptions)) + { + extractImages = false; + } + + var averageChapterDuration = GetAverageDurationBetweenChapters(chapters); + var threshold = TimeSpan.FromSeconds(1).Ticks; + if (averageChapterDuration < threshold) + { + _logger.LogInformation("Skipping chapter image extraction for {Video} as the average chapter duration {AverageDuration} was lower than the minimum threshold {Threshold}", video.Name, averageChapterDuration, threshold); + extractImages = false; + } + + var success = true; + var changesMade = false; + + var runtimeTicks = video.RunTimeTicks ?? 0; + + var currentImages = GetSavedChapterImages(video, directoryService); + + foreach (var chapter in chapters) + { + if (chapter.StartPositionTicks >= runtimeTicks) + { + _logger.LogInformation("Stopping chapter extraction for {0} because a chapter was found with a position greater than the runtime.", video.Name); + break; + } + + var path = _pathManager.GetChapterImagePath(video, chapter.StartPositionTicks); + + if (!currentImages.Contains(path, StringComparison.OrdinalIgnoreCase)) + { + if (extractImages) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + // Add some time for the first chapter to make sure we don't end up with a black image + var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(_firstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks); + + var inputPath = video.Path; + var directoryPath = Path.GetDirectoryName(path); + if (!string.IsNullOrEmpty(directoryPath)) + { + Directory.CreateDirectory(directoryPath); + } + + var container = video.Container; + var mediaSource = new MediaSourceInfo + { + VideoType = video.VideoType, + IsoType = video.IsoType, + Protocol = video.PathProtocol ?? MediaProtocol.File, + }; + + _logger.LogInformation("Extracting chapter image for {Name} at {Path}", video.Name, inputPath); + var tempFile = await _encoder.ExtractVideoImage(inputPath, container, mediaSource, video.GetDefaultVideoStream(), video.Video3DFormat, time, cancellationToken).ConfigureAwait(false); + File.Copy(tempFile, path, true); + + try + { + _fileSystem.DeleteFile(tempFile); + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting temporary chapter image encoding file {Path}", tempFile); + } + + chapter.ImagePath = path; + chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path); + changesMade = true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error extracting chapter images for {0}", string.Join(',', video.Path)); + success = false; + break; + } + } + else if (!string.IsNullOrEmpty(chapter.ImagePath)) + { + chapter.ImagePath = null; + changesMade = true; + } + } + else if (!string.Equals(path, chapter.ImagePath, StringComparison.OrdinalIgnoreCase)) + { + chapter.ImagePath = path; + chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path); + changesMade = true; + } + else if (libraryOptions?.EnableChapterImageExtraction != true) + { + // We have an image for the current chapter but the user has disabled chapter image extraction -> delete this chapter's image + chapter.ImagePath = null; + changesMade = true; + } + } + + if (saveChapters && changesMade) + { + SaveChapters(video, chapters); + } + + DeleteDeadImages(currentImages, chapters); + + return success; + } + + /// <inheritdoc /> + public void SaveChapters(Video video, IReadOnlyList<ChapterInfo> chapters) + { + // Remove any chapters that are outside of the runtime of the video + var validChapters = chapters.Where(c => c.StartPositionTicks < video.RunTimeTicks).ToList(); + _chapterRepository.SaveChapters(video.Id, validChapters); + } + + /// <inheritdoc /> + public ChapterInfo? GetChapter(Guid baseItemId, int index) + { + return _chapterRepository.GetChapter(baseItemId, index); + } + + /// <inheritdoc /> + public IReadOnlyList<ChapterInfo> GetChapters(Guid baseItemId) + { + return _chapterRepository.GetChapters(baseItemId); + } + + /// <inheritdoc /> + public async Task DeleteChapterDataAsync(Guid itemId, CancellationToken cancellationToken) + { + await _chapterRepository.DeleteChaptersAsync(itemId, cancellationToken).ConfigureAwait(false); + } + + private IReadOnlyList<string> GetSavedChapterImages(Video video, IDirectoryService directoryService) + { + var path = _pathManager.GetChapterImageFolderPath(video); + if (!Directory.Exists(path)) + { + return []; + } + + try + { + return directoryService.GetFilePaths(path); + } + catch (IOException) + { + return []; + } + } + + private void DeleteDeadImages(IEnumerable<string> images, IEnumerable<ChapterInfo> chapters) + { + var existingImages = chapters.Select(i => i.ImagePath).Where(i => !string.IsNullOrEmpty(i)); + var deadImages = images + .Except(existingImages, StringComparer.OrdinalIgnoreCase) + .Where(i => BaseItem.SupportedImageExtensions.Contains(Path.GetExtension(i.AsSpan()), StringComparison.OrdinalIgnoreCase)) + .ToList(); + + foreach (var image in deadImages) + { + _logger.LogDebug("Deleting dead chapter image {Path}", image); + + try + { + _fileSystem.DeleteFile(image!); + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting {Path}.", image); + } + } + } +} diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs new file mode 100644 index 00000000..c31bb4fb --- /dev/null +++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; +using System.Linq; +using Emby.Server.Implementations.Images; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.Collections +{ + /// <summary> + /// A collection image provider. + /// </summary> + public class CollectionImageProvider : BaseDynamicImageProvider<BoxSet> + { + /// <summary> + /// Initializes a new instance of the <see cref="CollectionImageProvider"/> class. + /// </summary> + /// <param name="fileSystem">The filesystem.</param> + /// <param name="providerManager">The provider manager.</param> + /// <param name="applicationPaths">The application paths.</param> + /// <param name="imageProcessor">The image processor.</param> + public CollectionImageProvider( + IFileSystem fileSystem, + IProviderManager providerManager, + IApplicationPaths applicationPaths, + IImageProcessor imageProcessor) + : base(fileSystem, providerManager, applicationPaths, imageProcessor) + { + } + + /// <inheritdoc /> + protected override bool Supports(BaseItem item) + { + // Right now this is the only way to prevent this image from getting created ahead of internet image providers + if (!item.IsLocked) + { + return false; + } + + return base.Supports(item); + } + + /// <inheritdoc /> + protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item) + { + var playlist = (BoxSet)item; + + return playlist.Children.Concat(playlist.GetLinkedChildren()) + .Select(i => + { + var subItem = i; + + var episode = subItem as Episode; + + var series = episode?.Series; + if (series is not null && series.HasImage(ImageType.Primary)) + { + return series; + } + + if (subItem.HasImage(ImageType.Primary)) + { + return subItem; + } + + var parent = subItem.GetOwner() ?? subItem.GetParent(); + + if (parent is not null && parent.HasImage(ImageType.Primary)) + { + if (parent is MusicAlbum) + { + return parent; + } + } + + return null; + }) + .Where(i => i is not null) + .GroupBy(x => x!.Id) // We removed the null values + .Select(x => x.First()) + .ToList()!; // Again... the list doesn't contain any null values + } + + /// <inheritdoc /> + protected override string CreateImage(BaseItem item, IReadOnlyCollection<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + { + return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary); + } + } +} diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs new file mode 100644 index 00000000..a320a774 --- /dev/null +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -0,0 +1,369 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Collections; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Collections +{ + /// <summary> + /// The collection manager. + /// </summary> + public class CollectionManager : ICollectionManager + { + private readonly ILibraryManager _libraryManager; + private readonly IFileSystem _fileSystem; + private readonly ILibraryMonitor _iLibraryMonitor; + private readonly ILogger<CollectionManager> _logger; + private readonly IProviderManager _providerManager; + private readonly ILocalizationManager _localizationManager; + private readonly IApplicationPaths _appPaths; + + /// <summary> + /// Initializes a new instance of the <see cref="CollectionManager"/> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="appPaths">The application paths.</param> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="fileSystem">The filesystem.</param> + /// <param name="iLibraryMonitor">The library monitor.</param> + /// <param name="loggerFactory">The logger factory.</param> + /// <param name="providerManager">The provider manager.</param> + public CollectionManager( + ILibraryManager libraryManager, + IApplicationPaths appPaths, + ILocalizationManager localizationManager, + IFileSystem fileSystem, + ILibraryMonitor iLibraryMonitor, + ILoggerFactory loggerFactory, + IProviderManager providerManager) + { + _libraryManager = libraryManager; + _fileSystem = fileSystem; + _iLibraryMonitor = iLibraryMonitor; + _logger = loggerFactory.CreateLogger<CollectionManager>(); + _providerManager = providerManager; + _localizationManager = localizationManager; + _appPaths = appPaths; + } + + /// <inheritdoc /> + public event EventHandler<CollectionCreatedEventArgs>? CollectionCreated; + + /// <inheritdoc /> + public event EventHandler<CollectionModifiedEventArgs>? ItemsAddedToCollection; + + /// <inheritdoc /> + public event EventHandler<CollectionModifiedEventArgs>? ItemsRemovedFromCollection; + + private IEnumerable<Folder> FindFolders(string path) + { + return _libraryManager + .RootFolder + .Children + .OfType<Folder>() + .Where(i => _fileSystem.AreEqual(path, i.Path) || _fileSystem.ContainsSubPath(i.Path, path)); + } + + internal async Task<Folder?> EnsureLibraryFolder(string path, bool createIfNeeded) + { + var existingFolder = FindFolders(path).FirstOrDefault(); + if (existingFolder is not null) + { + return existingFolder; + } + + if (!createIfNeeded) + { + return null; + } + + Directory.CreateDirectory(path); + + var libraryOptions = new LibraryOptions + { + PathInfos = [new MediaPathInfo(path)], + EnableRealtimeMonitor = false, + SaveLocalMetadata = true + }; + + var name = _localizationManager.GetLocalizedString("Collections"); + + await _libraryManager.AddVirtualFolder(name, CollectionTypeOptions.boxsets, libraryOptions, true).ConfigureAwait(false); + + _libraryManager.RootFolder.Children = null; + + return FindFolders(path).First(); + } + + internal string GetCollectionsFolderPath() + { + return Path.Combine(_appPaths.DataPath, "collections"); + } + + /// <inheritdoc /> + public Task<Folder?> GetCollectionsFolder(bool createIfNeeded) + { + return EnsureLibraryFolder(GetCollectionsFolderPath(), createIfNeeded); + } + + private IEnumerable<BoxSet> GetCollections(User user) + { + var folder = GetCollectionsFolder(false).GetAwaiter().GetResult(); + + return folder is null + ? Enumerable.Empty<BoxSet>() + : folder.GetChildren(user, true).OfType<BoxSet>(); + } + + /// <inheritdoc /> + public async Task<BoxSet> CreateCollectionAsync(CollectionCreationOptions options) + { + var name = options.Name; + + // Need to use the [boxset] suffix + // If internet metadata is not found, or if xml saving is off there will be no collection.xml + // This could cause it to get re-resolved as a plain folder + var folderName = _fileSystem.GetValidFilename(name) + " [boxset]"; + + var parentFolder = await GetCollectionsFolder(true).ConfigureAwait(false); + + if (parentFolder is null) + { + throw new ArgumentException(nameof(parentFolder)); + } + + var path = Path.Combine(parentFolder.Path, folderName); + + _iLibraryMonitor.ReportFileSystemChangeBeginning(path); + + try + { + var info = Directory.CreateDirectory(path); + var collection = new BoxSet + { + Name = name, + Path = path, + IsLocked = options.IsLocked, + ProviderIds = options.ProviderIds, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc + }; + + parentFolder.AddChild(collection); + + if (options.ItemIdList.Count > 0) + { + await AddToCollectionAsync( + collection.Id, + options.ItemIdList.Select(x => new Guid(x)), + false, + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + // The initial adding of items is going to create a local metadata file + // This will cause internet metadata to be skipped as a result + MetadataRefreshMode = MetadataRefreshMode.FullRefresh + }).ConfigureAwait(false); + } + else + { + _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); + } + + CollectionCreated?.Invoke(this, new CollectionCreatedEventArgs + { + Collection = collection, + Options = options + }); + + return collection; + } + finally + { + // Refresh handled internally + _iLibraryMonitor.ReportFileSystemChangeComplete(path, false); + } + } + + /// <inheritdoc /> + public Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> itemIds) + => AddToCollectionAsync(collectionId, itemIds, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem))); + + private async Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids, bool fireEvent, MetadataRefreshOptions refreshOptions) + { + if (_libraryManager.GetItemById(collectionId) is not BoxSet collection) + { + throw new ArgumentException("No collection exists with the supplied collectionId " + collectionId); + } + + List<BaseItem>? itemList = null; + + var linkedChildrenList = collection.GetLinkedChildren(); + var currentLinkedChildrenIds = linkedChildrenList.Select(i => i.Id).ToList(); + + foreach (var id in ids) + { + var item = _libraryManager.GetItemById(id); + + if (item is null) + { + throw new ArgumentException("No item exists with the supplied Id " + id); + } + + if (!currentLinkedChildrenIds.Contains(id)) + { + (itemList ??= new()).Add(item); + + linkedChildrenList.Add(item); + } + } + + if (itemList is not null) + { + var originalLen = collection.LinkedChildren.Length; + var newItemCount = itemList.Count; + LinkedChild[] newChildren = new LinkedChild[originalLen + newItemCount]; + collection.LinkedChildren.CopyTo(newChildren, 0); + for (int i = 0; i < newItemCount; i++) + { + newChildren[originalLen + i] = LinkedChild.Create(itemList[i]); + } + + collection.LinkedChildren = newChildren; + collection.UpdateRatingToItems(linkedChildrenList); + + await collection.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + + refreshOptions.ForceSave = true; + _providerManager.QueueRefresh(collection.Id, refreshOptions, RefreshPriority.High); + + if (fireEvent) + { + ItemsAddedToCollection?.Invoke(this, new CollectionModifiedEventArgs(collection, itemList)); + } + } + } + + /// <inheritdoc /> + public async Task RemoveFromCollectionAsync(Guid collectionId, IEnumerable<Guid> itemIds) + { + if (_libraryManager.GetItemById(collectionId) is not BoxSet collection) + { + throw new ArgumentException("No collection exists with the supplied Id"); + } + + var list = new List<LinkedChild>(); + var itemList = new List<BaseItem>(); + + foreach (var guidId in itemIds) + { + var childItem = _libraryManager.GetItemById(guidId); + + var child = collection.LinkedChildren.FirstOrDefault(i => (i.ItemId.HasValue && i.ItemId.Value.Equals(guidId)) || (childItem is not null && string.Equals(childItem.Path, i.Path, StringComparison.OrdinalIgnoreCase))); + + if (child is null) + { + _logger.LogWarning("No collection title exists with the supplied Id"); + continue; + } + + list.Add(child); + + if (childItem is not null) + { + itemList.Add(childItem); + } + } + + if (list.Count > 0) + { + collection.LinkedChildren = collection.LinkedChildren.Except(list).ToArray(); + } + + await collection.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + _providerManager.QueueRefresh( + collection.Id, + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + ForceSave = true + }, + RefreshPriority.High); + + ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs(collection, itemList)); + } + + /// <inheritdoc /> + public IEnumerable<BaseItem> CollapseItemsWithinBoxSets(IEnumerable<BaseItem> items, User user) + { + var results = new Dictionary<Guid, BaseItem>(); + + var allBoxSets = GetCollections(user).ToList(); + + foreach (var item in items) + { + if (item is ISupportsBoxSetGrouping) + { + var itemId = item.Id; + + var itemIsInBoxSet = false; + foreach (var boxSet in allBoxSets) + { + if (!boxSet.ContainsLinkedChildByItemId(itemId)) + { + continue; + } + + itemIsInBoxSet = true; + + results.TryAdd(boxSet.Id, boxSet); + } + + // skip any item that is in a box set + if (itemIsInBoxSet) + { + continue; + } + + var alreadyInResults = false; + + // this is kind of a performance hack because only Video has alternate versions that should be in a box set? + if (item is Video video) + { + foreach (var childId in video.GetLocalAlternateVersionIds()) + { + if (!results.ContainsKey(childId)) + { + continue; + } + + alreadyInResults = true; + break; + } + } + + if (alreadyInResults) + { + continue; + } + } + + results[item.Id] = item; + } + + return results.Values; + } + } +} diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs new file mode 100644 index 00000000..0ee43ce0 --- /dev/null +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -0,0 +1,123 @@ +using System; +using System.Globalization; +using System.IO; +using Emby.Server.Implementations.AppBase; +using Jellyfin.Data.Events; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Configuration +{ + /// <summary> + /// Class ServerConfigurationManager. + /// </summary> + public class ServerConfigurationManager : BaseConfigurationManager, IServerConfigurationManager + { + /// <summary> + /// Initializes a new instance of the <see cref="ServerConfigurationManager" /> class. + /// </summary> + /// <param name="applicationPaths">The application paths.</param> + /// <param name="loggerFactory">The logger factory.</param> + /// <param name="xmlSerializer">The XML serializer.</param> + public ServerConfigurationManager( + IApplicationPaths applicationPaths, + ILoggerFactory loggerFactory, + IXmlSerializer xmlSerializer) + : base(applicationPaths, loggerFactory, xmlSerializer) + { + UpdateMetadataPath(); + } + + /// <summary> + /// Configuration updating event. + /// </summary> + public event EventHandler<GenericEventArgs<ServerConfiguration>>? ConfigurationUpdating; + + /// <summary> + /// Gets the type of the configuration. + /// </summary> + /// <value>The type of the configuration.</value> + protected override Type ConfigurationType => typeof(ServerConfiguration); + + /// <summary> + /// Gets the application paths. + /// </summary> + /// <value>The application paths.</value> + public IServerApplicationPaths ApplicationPaths => (IServerApplicationPaths)CommonApplicationPaths; + + /// <summary> + /// Gets the configuration. + /// </summary> + /// <value>The configuration.</value> + public ServerConfiguration Configuration => (ServerConfiguration)CommonConfiguration; + + /// <summary> + /// Called when [configuration updated]. + /// </summary> + protected override void OnConfigurationUpdated() + { + UpdateMetadataPath(); + + base.OnConfigurationUpdated(); + } + + /// <summary> + /// Updates the metadata path. + /// </summary> + /// <exception cref="UnauthorizedAccessException">If the directory does not exist, and the caller does not have the required permission to create it.</exception> + /// <exception cref="NotSupportedException">If there is a custom path transcoding path specified, but it is invalid.</exception> + /// <exception cref="IOException">If the directory does not exist, and it also could not be created.</exception> + private void UpdateMetadataPath() + { + ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = string.IsNullOrWhiteSpace(Configuration.MetadataPath) + ? ApplicationPaths.DefaultInternalMetadataPath + : Configuration.MetadataPath; + Directory.CreateDirectory(ApplicationPaths.InternalMetadataPath); + } + + /// <summary> + /// Replaces the configuration. + /// </summary> + /// <param name="newConfiguration">The new configuration.</param> + /// <exception cref="DirectoryNotFoundException">If the configuration path doesn't exist.</exception> + public override void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration) + { + var newConfig = (ServerConfiguration)newConfiguration; + + ValidateMetadataPath(newConfig); + + ConfigurationUpdating?.Invoke(this, new GenericEventArgs<ServerConfiguration>(newConfig)); + + base.ReplaceConfiguration(newConfiguration); + } + + /// <summary> + /// Validates the metadata path. + /// </summary> + /// <param name="newConfig">The new configuration.</param> + /// <exception cref="DirectoryNotFoundException">The new config path doesn't exist.</exception> + private void ValidateMetadataPath(ServerConfiguration newConfig) + { + var newPath = newConfig.MetadataPath; + + if (!string.IsNullOrWhiteSpace(newPath) + && !string.Equals(Configuration.MetadataPath, newPath, StringComparison.Ordinal)) + { + if (!Directory.Exists(newPath)) + { + throw new DirectoryNotFoundException( + string.Format( + CultureInfo.InvariantCulture, + "{0} does not exist.", + newPath)); + } + + EnsureWriteAccess(newPath); + } + } + } +} diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs new file mode 100644 index 00000000..a06f6e7f --- /dev/null +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; + +namespace Emby.Server.Implementations +{ + /// <summary> + /// Static class containing the default configuration options for the web server. + /// </summary> + public static class ConfigurationOptions + { + /// <summary> + /// Gets a new copy of the default configuration options. + /// </summary> + public static Dictionary<string, string?> DefaultConfiguration => new() + { + { HostWebClientKey, bool.TrueString }, + { DefaultRedirectKey, "web/" }, + { FfmpegProbeSizeKey, "1G" }, + { FfmpegAnalyzeDurationKey, "200M" }, + { BindToUnixSocketKey, bool.FalseString }, + { SqliteCacheSizeKey, "20000" }, + { FfmpegSkipValidationKey, bool.FalseString }, + { FfmpegImgExtractPerfTradeoffKey, bool.FalseString }, + { DetectNetworkChangeKey, bool.TrueString } + }; + } +} diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs new file mode 100644 index 00000000..0381c4d3 --- /dev/null +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Security.Cryptography; +using MediaBrowser.Model.Cryptography; +using static MediaBrowser.Model.Cryptography.Constants; + +namespace Emby.Server.Implementations.Cryptography +{ + /// <summary> + /// Class providing abstractions over cryptographic functions. + /// </summary> + public class CryptographyProvider : ICryptoProvider + { + /// <inheritdoc /> + public string DefaultHashMethod => "PBKDF2-SHA512"; + + /// <inheritdoc /> + public PasswordHash CreatePasswordHash(ReadOnlySpan<char> password) + { + byte[] salt = GenerateSalt(); + return new PasswordHash( + DefaultHashMethod, + Rfc2898DeriveBytes.Pbkdf2( + password, + salt, + DefaultIterations, + HashAlgorithmName.SHA512, + DefaultOutputLength), + salt, + new Dictionary<string, string> + { + { "iterations", DefaultIterations.ToString(CultureInfo.InvariantCulture) } + }); + } + + /// <inheritdoc /> + public bool Verify(PasswordHash hash, ReadOnlySpan<char> password) + { + if (string.Equals(hash.Id, "PBKDF2", StringComparison.Ordinal)) + { + var iterations = GetIterationsParameter(hash); + return hash.Hash.SequenceEqual( + Rfc2898DeriveBytes.Pbkdf2( + password, + hash.Salt, + iterations, + HashAlgorithmName.SHA1, + 32)); + } + + if (string.Equals(hash.Id, "PBKDF2-SHA512", StringComparison.Ordinal)) + { + var iterations = GetIterationsParameter(hash); + return hash.Hash.SequenceEqual( + Rfc2898DeriveBytes.Pbkdf2( + password, + hash.Salt, + iterations, + HashAlgorithmName.SHA512, + DefaultOutputLength)); + } + + throw new NotSupportedException($"Can't verify hash with id: {hash.Id}"); + } + + /// <summary> + /// Extracts and validates the iterations parameter from a password hash. + /// </summary> + /// <param name="hash">The password hash containing parameters.</param> + /// <returns>The number of iterations.</returns> + /// <exception cref="FormatException">Thrown when iterations parameter is missing or invalid.</exception> + private static int GetIterationsParameter(PasswordHash hash) + { + if (!hash.Parameters.TryGetValue("iterations", out var iterationsStr)) + { + throw new FormatException($"Password hash with id '{hash.Id}' is missing required 'iterations' parameter."); + } + + if (!int.TryParse(iterationsStr, CultureInfo.InvariantCulture, out var iterations)) + { + throw new FormatException($"Password hash with id '{hash.Id}' has invalid 'iterations' parameter: '{iterationsStr}'."); + } + + return iterations; + } + + /// <inheritdoc /> + public byte[] GenerateSalt() + => GenerateSalt(DefaultSaltLength); + + /// <inheritdoc /> + public byte[] GenerateSalt(int length) + { + var salt = new byte[length]; + using var rng = RandomNumberGenerator.Create(); + rng.GetNonZeroBytes(salt); + return salt; + } + } +} diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs new file mode 100644 index 00000000..676bb7f8 --- /dev/null +++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs @@ -0,0 +1,119 @@ +#pragma warning disable CS1591 + +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Data; + +public class CleanDatabaseScheduledTask : ILibraryPostScanTask +{ + private readonly ILibraryManager _libraryManager; + private readonly ILogger<CleanDatabaseScheduledTask> _logger; + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + private readonly IPathManager _pathManager; + + public CleanDatabaseScheduledTask( + ILibraryManager libraryManager, + ILogger<CleanDatabaseScheduledTask> logger, + IDbContextFactory<JellyfinDbContext> dbProvider, + IPathManager pathManager) + { + _libraryManager = libraryManager; + _logger = logger; + _dbProvider = dbProvider; + _pathManager = pathManager; + } + + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + await CleanDeadItems(cancellationToken, progress).ConfigureAwait(false); + } + + private async Task CleanDeadItems(CancellationToken cancellationToken, IProgress<double> progress) + { + var itemIds = _libraryManager.GetItemIds(new InternalItemsQuery + { + HasDeadParentId = true + }); + + var numComplete = 0; + var numItems = itemIds.Count + 1; + + _logger.LogDebug("Cleaning {Number} items with dead parents", numItems); + + IProgress<double> subProgress = new Progress<double>((val) => progress.Report(val / 2)); + + foreach (var itemId in itemIds) + { + cancellationToken.ThrowIfCancellationRequested(); + + var item = _libraryManager.GetItemById(itemId); + if (item is not null) + { + _logger.LogInformation("Cleaning item {Item} type: {Type} path: {Path}", item.Name, item.GetType().Name, item.Path ?? string.Empty); + + foreach (var mediaSource in item.GetMediaSources(false)) + { + // Delete extracted data + var mediaSourceItem = _libraryManager.GetItemById(mediaSource.Id); + if (mediaSourceItem is null) + { + continue; + } + + var extractedDataFolders = _pathManager.GetExtractedDataPaths(mediaSourceItem); + foreach (var folder in extractedDataFolders) + { + if (Directory.Exists(folder)) + { + try + { + Directory.Delete(folder, true); + } + catch (Exception e) + { + _logger.LogWarning("Failed to remove {Folder}: {Exception}", folder, e.Message); + } + } + } + } + + // Delete item + _libraryManager.DeleteItem(item, new DeleteOptions + { + DeleteFileLocation = false + }); + } + + numComplete++; + double percent = numComplete; + percent /= numItems; + subProgress.Report(percent * 100); + } + + subProgress = new Progress<double>((val) => progress.Report((val / 2) + 50)); + var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + await using (context.ConfigureAwait(false)) + { + var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false); + await using (transaction.ConfigureAwait(false)) + { + await context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false); + subProgress.Report(50); + await transaction.CommitAsync(cancellationToken).ConfigureAwait(false); + subProgress.Report(100); + } + } + + progress.Report(100); + } +} diff --git a/Emby.Server.Implementations/Data/ItemTypeLookup.cs b/Emby.Server.Implementations/Data/ItemTypeLookup.cs new file mode 100644 index 00000000..82c0a8b6 --- /dev/null +++ b/Emby.Server.Implementations/Data/ItemTypeLookup.cs @@ -0,0 +1,64 @@ +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Threading.Channels; +using Emby.Server.Implementations.Playlists; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Playlists; + +namespace Emby.Server.Implementations.Data; + +/// <inheritdoc /> +public class ItemTypeLookup : IItemTypeLookup +{ + /// <inheritdoc /> + public IReadOnlyList<string> MusicGenreTypes { get; } = [ + typeof(Audio).FullName!, + typeof(MusicVideo).FullName!, + typeof(MusicAlbum).FullName!, + typeof(MusicArtist).FullName!, + ]; + + /// <inheritdoc /> + public IReadOnlyDictionary<BaseItemKind, string> BaseItemKindNames { get; } = new Dictionary<BaseItemKind, string>() + { + { BaseItemKind.AggregateFolder, typeof(AggregateFolder).FullName! }, + { BaseItemKind.Audio, typeof(Audio).FullName! }, + { BaseItemKind.AudioBook, typeof(AudioBook).FullName! }, + { BaseItemKind.BasePluginFolder, typeof(BasePluginFolder).FullName! }, + { BaseItemKind.Book, typeof(Book).FullName! }, + { BaseItemKind.BoxSet, typeof(BoxSet).FullName! }, + { BaseItemKind.Channel, typeof(Channel).FullName! }, + { BaseItemKind.CollectionFolder, typeof(CollectionFolder).FullName! }, + { BaseItemKind.Episode, typeof(Episode).FullName! }, + { BaseItemKind.Folder, typeof(Folder).FullName! }, + { BaseItemKind.Genre, typeof(Genre).FullName! }, + { BaseItemKind.Movie, typeof(Movie).FullName! }, + { BaseItemKind.LiveTvChannel, typeof(LiveTvChannel).FullName! }, + { BaseItemKind.LiveTvProgram, typeof(LiveTvProgram).FullName! }, + { BaseItemKind.MusicAlbum, typeof(MusicAlbum).FullName! }, + { BaseItemKind.MusicArtist, typeof(MusicArtist).FullName! }, + { BaseItemKind.MusicGenre, typeof(MusicGenre).FullName! }, + { BaseItemKind.MusicVideo, typeof(MusicVideo).FullName! }, + { BaseItemKind.Person, typeof(Person).FullName! }, + { BaseItemKind.Photo, typeof(Photo).FullName! }, + { BaseItemKind.PhotoAlbum, typeof(PhotoAlbum).FullName! }, + { BaseItemKind.Playlist, typeof(Playlist).FullName! }, + { BaseItemKind.PlaylistsFolder, typeof(PlaylistsFolder).FullName! }, + { BaseItemKind.Season, typeof(Season).FullName! }, + { BaseItemKind.Series, typeof(Series).FullName! }, + { BaseItemKind.Studio, typeof(Studio).FullName! }, + { BaseItemKind.Trailer, typeof(Trailer).FullName! }, + { BaseItemKind.TvChannel, typeof(LiveTvChannel).FullName! }, + { BaseItemKind.TvProgram, typeof(LiveTvProgram).FullName! }, + { BaseItemKind.UserRootFolder, typeof(UserRootFolder).FullName! }, + { BaseItemKind.UserView, typeof(UserView).FullName! }, + { BaseItemKind.Video, typeof(Video).FullName! }, + { BaseItemKind.Year, typeof(Year).FullName! } + }.ToFrozenDictionary(); +} diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs new file mode 100644 index 00000000..0efef4de --- /dev/null +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -0,0 +1,271 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using Microsoft.Data.Sqlite; + +namespace Emby.Server.Implementations.Data +{ + public static class SqliteExtensions + { + private const string DatetimeFormatUtc = "yyyy-MM-dd HH:mm:ss.FFFFFFFK"; + private const string DatetimeFormatLocal = "yyyy-MM-dd HH:mm:ss.FFFFFFF"; + + /// <summary> + /// An array of ISO-8601 DateTime formats that we support parsing. + /// </summary> + private static readonly string[] _datetimeFormats = new string[] + { + "THHmmssK", + "THHmmK", + "HH:mm:ss.FFFFFFFK", + "HH:mm:ssK", + "HH:mmK", + DatetimeFormatUtc, + "yyyy-MM-dd HH:mm:ssK", + "yyyy-MM-dd HH:mmK", + "yyyy-MM-ddTHH:mm:ss.FFFFFFFK", + "yyyy-MM-ddTHH:mmK", + "yyyy-MM-ddTHH:mm:ssK", + "yyyyMMddHHmmssK", + "yyyyMMddHHmmK", + "yyyyMMddTHHmmssFFFFFFFK", + "THHmmss", + "THHmm", + "HH:mm:ss.FFFFFFF", + "HH:mm:ss", + "HH:mm", + DatetimeFormatLocal, + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd HH:mm", + "yyyy-MM-ddTHH:mm:ss.FFFFFFF", + "yyyy-MM-ddTHH:mm", + "yyyy-MM-ddTHH:mm:ss", + "yyyyMMddHHmmss", + "yyyyMMddHHmm", + "yyyyMMddTHHmmssFFFFFFF", + "yyyy-MM-dd", + "yyyyMMdd", + "yy-MM-dd" + }; + + public static IEnumerable<SqliteDataReader> Query(this SqliteConnection sqliteConnection, string commandText) + { + if (sqliteConnection.State != ConnectionState.Open) + { + sqliteConnection.Open(); + } + + using var command = sqliteConnection.CreateCommand(); + command.CommandText = commandText; + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + yield return reader; + } + } + } + + public static void Execute(this SqliteConnection sqliteConnection, string commandText) + { + using var command = sqliteConnection.CreateCommand(); + command.CommandText = commandText; + command.ExecuteNonQuery(); + } + + public static string ToDateTimeParamValue(this DateTime dateValue) + { + var kind = DateTimeKind.Utc; + + return (dateValue.Kind == DateTimeKind.Unspecified) + ? DateTime.SpecifyKind(dateValue, kind).ToString( + GetDateTimeKindFormat(kind), + CultureInfo.InvariantCulture) + : dateValue.ToString( + GetDateTimeKindFormat(dateValue.Kind), + CultureInfo.InvariantCulture); + } + + private static string GetDateTimeKindFormat(DateTimeKind kind) + => (kind == DateTimeKind.Utc) ? DatetimeFormatUtc : DatetimeFormatLocal; + + public static bool TryReadDateTime(this SqliteDataReader reader, int index, out DateTime result) + { + if (reader.IsDBNull(index)) + { + result = default; + return false; + } + + var dateText = reader.GetString(index); + + if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AdjustToUniversal, out var dateTimeResult)) + { + // If the resulting DateTimeKind is Unspecified it is actually Utc. + // This is required downstream for the Json serializer. + if (dateTimeResult.Kind == DateTimeKind.Unspecified) + { + dateTimeResult = DateTime.SpecifyKind(dateTimeResult, DateTimeKind.Utc); + } + + result = dateTimeResult; + return true; + } + + result = default; + return false; + } + + public static bool TryGetGuid(this SqliteDataReader reader, int index, out Guid result) + { + if (reader.IsDBNull(index)) + { + result = default; + return false; + } + + try + { + result = reader.GetGuid(index); + return true; + } + catch + { + result = Guid.Empty; + return false; + } + } + + public static bool TryGetString(this SqliteDataReader reader, int index, out string result) + { + result = string.Empty; + + if (reader.IsDBNull(index)) + { + return false; + } + + result = reader.GetString(index); + return true; + } + + public static bool TryGetBoolean(this SqliteDataReader reader, int index, out bool result) + { + if (reader.IsDBNull(index)) + { + result = default; + return false; + } + + result = reader.GetBoolean(index); + return true; + } + + public static bool TryGetInt32(this SqliteDataReader reader, int index, out int result) + { + if (reader.IsDBNull(index)) + { + result = default; + return false; + } + + result = reader.GetInt32(index); + return true; + } + + public static bool TryGetInt64(this SqliteDataReader reader, int index, out long result) + { + if (reader.IsDBNull(index)) + { + result = default; + return false; + } + + result = reader.GetInt64(index); + return true; + } + + public static bool TryGetSingle(this SqliteDataReader reader, int index, out float result) + { + if (reader.IsDBNull(index)) + { + result = default; + return false; + } + + result = reader.GetFloat(index); + return true; + } + + public static bool TryGetDouble(this SqliteDataReader reader, int index, out double result) + { + if (reader.IsDBNull(index)) + { + result = default; + return false; + } + + result = reader.GetDouble(index); + return true; + } + + public static void TryBind(this SqliteCommand statement, string name, Guid value) + { + statement.TryBind(name, value, true); + } + + public static void TryBind(this SqliteCommand statement, string name, object? value, bool isBlob = false) + { + var preparedValue = value ?? DBNull.Value; + if (statement.Parameters.Contains(name)) + { + statement.Parameters[name].Value = preparedValue; + } + else + { + // Blobs aren't always detected automatically + if (isBlob) + { + statement.Parameters.Add(new SqliteParameter(name, SqliteType.Blob) { Value = value }); + } + else + { + statement.Parameters.AddWithValue(name, preparedValue); + } + } + } + + public static void TryBindNull(this SqliteCommand statement, string name) + { + statement.TryBind(name, DBNull.Value); + } + + public static IEnumerable<SqliteDataReader> ExecuteQuery(this SqliteCommand command) + { + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + yield return reader; + } + } + } + + public static int SelectScalarInt(this SqliteCommand command) + { + var result = command.ExecuteScalar(); + // Can't be null since the method is used to retrieve Count + return Convert.ToInt32(result!, CultureInfo.InvariantCulture); + } + + public static SqliteCommand PrepareStatement(this SqliteConnection sqliteConnection, string sql) + { + var command = sqliteConnection.CreateCommand(); + command.CommandText = sql; + return command; + } + } +} diff --git a/Emby.Server.Implementations/Data/TypeMapper.cs b/Emby.Server.Implementations/Data/TypeMapper.cs new file mode 100644 index 00000000..9efcea84 --- /dev/null +++ b/Emby.Server.Implementations/Data/TypeMapper.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; + +namespace Emby.Server.Implementations.Data +{ + /// <summary> + /// Class TypeMapper. + /// </summary> + public class TypeMapper + { + /// <summary> + /// This holds all the types in the running assemblies + /// so that we can de-serialize properly when we don't have strong types. + /// </summary> + private readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>(); + + /// <summary> + /// Gets the type. + /// </summary> + /// <param name="typeName">Name of the type.</param> + /// <returns>Type.</returns> + /// <exception cref="ArgumentNullException"><c>typeName</c> is null.</exception> + public Type? GetType(string typeName) + { + ArgumentException.ThrowIfNullOrEmpty(typeName); + + return _typeMap.GetOrAdd(typeName, k => AppDomain.CurrentDomain.GetAssemblies() + .Select(a => a.GetType(k)) + .FirstOrDefault(t => t is not null)); + } + } +} diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs new file mode 100644 index 00000000..0b3c3bbd --- /dev/null +++ b/Emby.Server.Implementations/Devices/DeviceId.cs @@ -0,0 +1,98 @@ +#pragma warning disable CS1591 + +using System; +using System.Globalization; +using System.IO; +using System.Text; +using System.Threading; +using MediaBrowser.Common.Configuration; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Devices +{ + public class DeviceId + { + private readonly IApplicationPaths _appPaths; + private readonly ILogger<DeviceId> _logger; + private readonly Lock _syncLock = new(); + + private string? _id; + + public DeviceId(IApplicationPaths appPaths, ILogger<DeviceId> logger) + { + _appPaths = appPaths; + _logger = logger; + } + + public string Value => _id ??= GetDeviceId(); + + private string CachePath => Path.Combine(_appPaths.DataPath, "device.txt"); + + private string? GetCachedId() + { + try + { + lock (_syncLock) + { + var value = File.ReadAllText(CachePath, Encoding.UTF8); + + if (Guid.TryParse(value, out _)) + { + return value; + } + + _logger.LogError("Invalid value found in device id file"); + } + } + catch (DirectoryNotFoundException) + { + } + catch (FileNotFoundException) + { + } + catch (Exception ex) + { + _logger.LogError(ex, "Error reading file"); + } + + return null; + } + + private void SaveId(string id) + { + try + { + var path = CachePath; + + Directory.CreateDirectory(Path.GetDirectoryName(path) ?? throw new InvalidOperationException("Path can't be a root directory.")); + + lock (_syncLock) + { + File.WriteAllText(path, id, Encoding.UTF8); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error writing to file"); + } + } + + private static string GetNewId() + { + return Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); + } + + private string GetDeviceId() + { + var id = GetCachedId(); + + if (string.IsNullOrWhiteSpace(id)) + { + id = GetNewId(); + SaveId(id); + } + + return id; + } + } +} diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs new file mode 100644 index 00000000..b392340f --- /dev/null +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -0,0 +1,1511 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Extensions; +using MediaBrowser.Common; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Chapters; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Playlists; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Trickplay; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using Microsoft.Extensions.Logging; +using Book = MediaBrowser.Controller.Entities.Book; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Movie = MediaBrowser.Controller.Entities.Movies.Movie; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; +using Person = MediaBrowser.Controller.Entities.Person; +using Photo = MediaBrowser.Controller.Entities.Photo; +using Season = MediaBrowser.Controller.Entities.TV.Season; +using Series = MediaBrowser.Controller.Entities.TV.Series; + +namespace Emby.Server.Implementations.Dto +{ + public class DtoService : IDtoService + { + private static readonly FrozenDictionary<BaseItemKind, BaseItemKind[]> _relatedItemKinds = new Dictionary<BaseItemKind, BaseItemKind[]> + { + { + BaseItemKind.Genre, [ + BaseItemKind.Audio, + BaseItemKind.Episode, + BaseItemKind.Movie, + BaseItemKind.LiveTvProgram, + BaseItemKind.MusicAlbum, + BaseItemKind.MusicArtist, + BaseItemKind.MusicVideo, + BaseItemKind.Series, + BaseItemKind.Trailer + ] + }, + { + BaseItemKind.MusicArtist, [ + BaseItemKind.Audio, + BaseItemKind.MusicAlbum, + BaseItemKind.MusicVideo + ] + }, + { + BaseItemKind.MusicGenre, [ + BaseItemKind.Audio, + BaseItemKind.MusicAlbum, + BaseItemKind.MusicArtist, + BaseItemKind.MusicVideo + ] + }, + { + BaseItemKind.Person, [ + BaseItemKind.Audio, + BaseItemKind.Episode, + BaseItemKind.Movie, + BaseItemKind.LiveTvProgram, + BaseItemKind.MusicAlbum, + BaseItemKind.MusicArtist, + BaseItemKind.MusicVideo, + BaseItemKind.Series, + BaseItemKind.Trailer + ] + }, + { + BaseItemKind.Studio, [ + BaseItemKind.Audio, + BaseItemKind.Episode, + BaseItemKind.Movie, + BaseItemKind.LiveTvProgram, + BaseItemKind.MusicAlbum, + BaseItemKind.MusicArtist, + BaseItemKind.MusicVideo, + BaseItemKind.Series, + BaseItemKind.Trailer + ] + }, + { + BaseItemKind.Year, [ + BaseItemKind.Audio, + BaseItemKind.Episode, + BaseItemKind.Movie, + BaseItemKind.LiveTvProgram, + BaseItemKind.MusicAlbum, + BaseItemKind.MusicArtist, + BaseItemKind.MusicVideo, + BaseItemKind.Series, + BaseItemKind.Trailer + ] + } + }.ToFrozenDictionary(); + + private readonly ILogger<DtoService> _logger; + private readonly ILibraryManager _libraryManager; + private readonly IUserDataManager _userDataRepository; + + private readonly IImageProcessor _imageProcessor; + private readonly IProviderManager _providerManager; + private readonly IRecordingsManager _recordingsManager; + + private readonly IApplicationHost _appHost; + private readonly IMediaSourceManager _mediaSourceManager; + private readonly Lazy<ILiveTvManager> _livetvManagerFactory; + + private readonly ITrickplayManager _trickplayManager; + private readonly IChapterManager _chapterManager; + + public DtoService( + ILogger<DtoService> logger, + ILibraryManager libraryManager, + IUserDataManager userDataRepository, + IImageProcessor imageProcessor, + IProviderManager providerManager, + IRecordingsManager recordingsManager, + IApplicationHost appHost, + IMediaSourceManager mediaSourceManager, + Lazy<ILiveTvManager> livetvManagerFactory, + ITrickplayManager trickplayManager, + IChapterManager chapterManager) + { + _logger = logger; + _libraryManager = libraryManager; + _userDataRepository = userDataRepository; + _imageProcessor = imageProcessor; + _providerManager = providerManager; + _recordingsManager = recordingsManager; + _appHost = appHost; + _mediaSourceManager = mediaSourceManager; + _livetvManagerFactory = livetvManagerFactory; + _trickplayManager = trickplayManager; + _chapterManager = chapterManager; + } + + private ILiveTvManager LivetvManager => _livetvManagerFactory.Value; + + /// <inheritdoc /> + public IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User? user = null, BaseItem? owner = null) + { + var accessibleItems = user is null ? items : items.Where(x => x.IsVisible(user)).ToList(); + var returnItems = new BaseItemDto[accessibleItems.Count]; + List<(BaseItem, BaseItemDto)>? programTuples = null; + List<(BaseItemDto, LiveTvChannel)>? channelTuples = null; + + for (int index = 0; index < accessibleItems.Count; index++) + { + var item = accessibleItems[index]; + var dto = GetBaseItemDtoInternal(item, options, user, owner); + + if (item is LiveTvChannel tvChannel) + { + (channelTuples ??= []).Add((dto, tvChannel)); + } + else if (item is LiveTvProgram) + { + (programTuples ??= []).Add((item, dto)); + } + + if (options.ContainsField(ItemFields.ItemCounts)) + { + SetItemByNameInfo(dto, user); + } + + returnItems[index] = dto; + } + + if (programTuples is not null) + { + LivetvManager.AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult(); + } + + if (channelTuples is not null) + { + LivetvManager.AddChannelInfo(channelTuples, options, user); + } + + return returnItems; + } + + public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User? user = null, BaseItem? owner = null) + { + var dto = GetBaseItemDtoInternal(item, options, user, owner); + if (item is LiveTvChannel tvChannel) + { + LivetvManager.AddChannelInfo(new[] { (dto, tvChannel) }, options, user); + } + else if (item is LiveTvProgram) + { + LivetvManager.AddInfoToProgramDto(new[] { (item, dto) }, options.Fields, user).GetAwaiter().GetResult(); + } + + if (options.ContainsField(ItemFields.ItemCounts)) + { + SetItemByNameInfo(dto, user); + } + + return dto; + } + + private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User? user = null, BaseItem? owner = null) + { + var dto = new BaseItemDto + { + ServerId = _appHost.SystemId + }; + + if (item.SourceType == SourceType.Channel) + { + dto.SourceType = item.SourceType.ToString(); + } + + if (options.ContainsField(ItemFields.People)) + { + AttachPeople(dto, item, user); + } + + if (options.ContainsField(ItemFields.PrimaryImageAspectRatio)) + { + try + { + AttachPrimaryImageAspectRatio(dto, item); + } + catch (Exception ex) + { + // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions + _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {ItemName}", item.Name); + } + } + + if (options.ContainsField(ItemFields.DisplayPreferencesId)) + { + dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N", CultureInfo.InvariantCulture); + } + + if (user is not null) + { + AttachUserSpecificInfo(dto, item, user, options); + } + + if (item is IHasMediaSources + && options.ContainsField(ItemFields.MediaSources)) + { + dto.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, true, user).ToArray(); + + NormalizeMediaSourceContainers(dto); + } + + if (options.ContainsField(ItemFields.Studios)) + { + AttachStudios(dto, item); + } + + AttachBasicFields(dto, item, owner, options); + + if (options.ContainsField(ItemFields.CanDelete)) + { + dto.CanDelete = user is null + ? item.CanDelete() + : item.CanDelete(user); + } + + if (options.ContainsField(ItemFields.CanDownload)) + { + dto.CanDownload = user is null + ? item.CanDownload() + : item.CanDownload(user); + } + + if (options.ContainsField(ItemFields.Etag)) + { + dto.Etag = item.GetEtag(user); + } + + var activeRecording = _recordingsManager.GetActiveRecordingInfo(item.Path); + if (activeRecording is not null) + { + dto.Type = BaseItemKind.Recording; + dto.CanDownload = false; + dto.RunTimeTicks = null; + + if (!string.IsNullOrEmpty(dto.SeriesName)) + { + dto.EpisodeTitle = dto.Name; + dto.Name = dto.SeriesName; + } + + LivetvManager.AddInfoToRecordingDto(item, dto, activeRecording, user); + } + + if (item is Audio audio) + { + dto.HasLyrics = audio.GetMediaStreams().Any(s => s.Type == MediaStreamType.Lyric); + } + + return dto; + } + + private static void NormalizeMediaSourceContainers(BaseItemDto dto) + { + foreach (var mediaSource in dto.MediaSources) + { + var container = mediaSource.Container; + if (string.IsNullOrEmpty(container)) + { + continue; + } + + var containers = container.Split(','); + if (containers.Length < 2) + { + continue; + } + + var path = mediaSource.Path; + string? fileExtensionContainer = null; + + if (!string.IsNullOrEmpty(path)) + { + path = Path.GetExtension(path); + if (!string.IsNullOrEmpty(path)) + { + path = Path.GetExtension(path); + if (!string.IsNullOrEmpty(path)) + { + path = path.TrimStart('.'); + } + + if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparison.OrdinalIgnoreCase)) + { + fileExtensionContainer = path; + } + } + } + + mediaSource.Container = fileExtensionContainer ?? containers[0]; + } + } + + /// <inheritdoc /> + /// TODO refactor this to use the new SetItemByNameInfo. + /// Some callers already have the counts extracted so no reason to retrieve them again. + public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem>? taggedItems, User? user = null) + { + var dto = GetBaseItemDtoInternal(item, options, user); + + if (options.ContainsField(ItemFields.ItemCounts) + && taggedItems is not null + && taggedItems.Count != 0) + { + SetItemByNameInfo(item, dto, taggedItems); + } + + return dto; + } + + private void SetItemByNameInfo(BaseItemDto dto, User? user) + { + if (!_relatedItemKinds.TryGetValue(dto.Type, out var relatedItemKinds)) + { + return; + } + + var query = new InternalItemsQuery(user) + { + Recursive = true, + DtoOptions = new DtoOptions(false) { EnableImages = false }, + IncludeItemTypes = relatedItemKinds + }; + + switch (dto.Type) + { + case BaseItemKind.Genre: + case BaseItemKind.MusicGenre: + query.GenreIds = [dto.Id]; + break; + case BaseItemKind.MusicArtist: + query.ArtistIds = [dto.Id]; + break; + case BaseItemKind.Person: + query.PersonIds = [dto.Id]; + break; + case BaseItemKind.Studio: + query.StudioIds = [dto.Id]; + break; + case BaseItemKind.Year + when int.TryParse(dto.Name, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year): + query.Years = [year]; + break; + default: + return; + } + + var counts = _libraryManager.GetItemCounts(query); + + dto.AlbumCount = counts.AlbumCount; + dto.ArtistCount = counts.ArtistCount; + dto.EpisodeCount = counts.EpisodeCount; + dto.MovieCount = counts.MovieCount; + dto.MusicVideoCount = counts.MusicVideoCount; + dto.ProgramCount = counts.ProgramCount; + dto.SeriesCount = counts.SeriesCount; + dto.SongCount = counts.SongCount; + dto.TrailerCount = counts.TrailerCount; + dto.ChildCount = counts.TotalItemCount(); + } + + private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IReadOnlyList<BaseItem> taggedItems) + { + if (item is MusicArtist) + { + dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum); + dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); + dto.SongCount = taggedItems.Count(i => i is Audio); + } + else if (item is MusicGenre) + { + dto.ArtistCount = taggedItems.Count(i => i is MusicArtist); + dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum); + dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); + dto.SongCount = taggedItems.Count(i => i is Audio); + } + else + { + // This populates them all and covers Genre, Person, Studio, Year + + dto.ArtistCount = taggedItems.Count(i => i is MusicArtist); + dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum); + dto.EpisodeCount = taggedItems.Count(i => i is Episode); + dto.MovieCount = taggedItems.Count(i => i is Movie); + dto.TrailerCount = taggedItems.Count(i => i is Trailer); + dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); + dto.SeriesCount = taggedItems.Count(i => i is Series); + dto.ProgramCount = taggedItems.Count(i => i is LiveTvProgram); + dto.SongCount = taggedItems.Count(i => i is Audio); + } + + dto.ChildCount = taggedItems.Count; + } + + /// <summary> + /// Attaches the user specific info. + /// </summary> + private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, DtoOptions options) + { + if (item.IsFolder) + { + var folder = (Folder)item; + + if (options.EnableUserData) + { + dto.UserData = _userDataRepository.GetUserDataDto(item, dto, user, options); + } + + if (!dto.ChildCount.HasValue && item.SourceType == SourceType.Library) + { + // For these types we can try to optimize and assume these values will be equal + if (item is MusicAlbum || item is Season || item is Playlist) + { + dto.ChildCount = dto.RecursiveItemCount; + var folderChildCount = folder.LinkedChildren.Length; + // The default is an empty array, so we can't reliably use the count when it's empty + if (folderChildCount > 0) + { + dto.ChildCount ??= folderChildCount; + } + } + + if (options.ContainsField(ItemFields.ChildCount)) + { + dto.ChildCount ??= GetChildCount(folder, user); + } + } + + if (options.ContainsField(ItemFields.CumulativeRunTimeTicks)) + { + dto.CumulativeRunTimeTicks = item.RunTimeTicks; + } + + if (options.ContainsField(ItemFields.DateLastMediaAdded)) + { + dto.DateLastMediaAdded = folder.DateLastMediaAdded; + } + } + else + { + if (options.EnableUserData) + { + dto.UserData = _userDataRepository.GetUserDataDto(item, user); + } + } + + if (options.ContainsField(ItemFields.PlayAccess)) + { + dto.PlayAccess = item.GetPlayAccess(user); + } + } + + private static int GetChildCount(Folder folder, User user) + { + // Right now this is too slow to calculate for top level folders on a per-user basis + // Just return something so that apps that are expecting a value won't think the folders are empty + if (folder is ICollectionFolder || folder is UserView) + { + return Random.Shared.Next(1, 10); + } + + return folder.GetChildCount(user); + } + + private static void SetBookProperties(BaseItemDto dto, Book item) + { + dto.SeriesName = item.SeriesName; + } + + private static void SetPhotoProperties(BaseItemDto dto, Photo item) + { + dto.CameraMake = item.CameraMake; + dto.CameraModel = item.CameraModel; + dto.Software = item.Software; + dto.ExposureTime = item.ExposureTime; + dto.FocalLength = item.FocalLength; + dto.ImageOrientation = item.Orientation; + dto.Aperture = item.Aperture; + dto.ShutterSpeed = item.ShutterSpeed; + + dto.Latitude = item.Latitude; + dto.Longitude = item.Longitude; + dto.Altitude = item.Altitude; + dto.IsoSpeedRating = item.IsoSpeedRating; + + var album = item.AlbumEntity; + + if (album is not null) + { + dto.Album = album.Name; + dto.AlbumId = album.Id; + } + } + + private void SetMusicVideoProperties(BaseItemDto dto, MusicVideo item) + { + if (!string.IsNullOrEmpty(item.Album)) + { + var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery + { + IncludeItemTypes = new[] { BaseItemKind.MusicAlbum }, + Name = item.Album, + Limit = 1 + }); + + if (parentAlbumIds.Count > 0) + { + dto.AlbumId = parentAlbumIds[0]; + } + } + + dto.Album = item.Album; + } + + private string[] GetImageTags(BaseItem item, List<ItemImageInfo> images) + { + return images + .Select(p => GetImageCacheTag(item, p)) + .Where(i => i is not null) + .ToArray()!; // null values got filtered out + } + + private string? GetImageCacheTag(BaseItem item, ItemImageInfo image) + { + try + { + return _imageProcessor.GetImageCacheTag(item, image); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting {ImageType} image info for {Path}", image.Type, image.Path); + return null; + } + } + + /// <summary> + /// Attaches People DTO's to a DTOBaseItem. + /// </summary> + /// <param name="dto">The dto.</param> + /// <param name="item">The item.</param> + /// <param name="user">The requesting user.</param> + private void AttachPeople(BaseItemDto dto, BaseItem item, User? user = null) + { + // Ordering by person type to ensure actors and artists are at the front. + // This is taking advantage of the fact that they both begin with A + // This should be improved in the future + var people = _libraryManager.GetPeople(item).OrderBy(i => i.SortOrder ?? int.MaxValue) + .ThenBy(i => + { + if (i.IsType(PersonKind.Actor)) + { + return 0; + } + + if (i.IsType(PersonKind.GuestStar)) + { + return 1; + } + + if (i.IsType(PersonKind.Director)) + { + return 2; + } + + if (i.IsType(PersonKind.Writer)) + { + return 3; + } + + if (i.IsType(PersonKind.Producer)) + { + return 4; + } + + if (i.IsType(PersonKind.Composer)) + { + return 4; + } + + return 10; + }) + .ToList(); + + var list = new List<BaseItemPerson>(); + + Dictionary<string, Person> dictionary = people.Select(p => p.Name) + .Distinct(StringComparer.OrdinalIgnoreCase).Select(c => + { + try + { + return _libraryManager.GetPerson(c); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting person {Name}", c); + return null; + } + }).Where(i => i is not null) + .Where(i => user is null || i!.IsVisible(user)) + .DistinctBy(x => x!.Name, StringComparer.OrdinalIgnoreCase) + .ToDictionary(i => i!.Name, StringComparer.OrdinalIgnoreCase)!; // null values got filtered out + + for (var i = 0; i < people.Count; i++) + { + var person = people[i]; + + var baseItemPerson = new BaseItemPerson + { + Name = person.Name, + Role = person.Role, + Type = person.Type + }; + + if (dictionary.TryGetValue(person.Name, out Person? entity)) + { + baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary); + baseItemPerson.Id = entity.Id; + if (dto.ImageBlurHashes is not null) + { + // Only add BlurHash for the person's image. + baseItemPerson.ImageBlurHashes = []; + foreach (var (imageType, blurHash) in dto.ImageBlurHashes) + { + if (blurHash is not null) + { + baseItemPerson.ImageBlurHashes[imageType] = []; + foreach (var (imageId, blurHashValue) in blurHash) + { + if (string.Equals(baseItemPerson.PrimaryImageTag, imageId, StringComparison.OrdinalIgnoreCase)) + { + baseItemPerson.ImageBlurHashes[imageType][imageId] = blurHashValue; + } + } + } + } + } + + list.Add(baseItemPerson); + } + } + + dto.People = list.ToArray(); + } + + /// <summary> + /// Attaches the studios. + /// </summary> + /// <param name="dto">The dto.</param> + /// <param name="item">The item.</param> + private void AttachStudios(BaseItemDto dto, BaseItem item) + { + dto.Studios = item.Studios + .Where(i => !string.IsNullOrEmpty(i)) + .Select(i => new NameGuidPair + { + Name = i, + Id = _libraryManager.GetStudioId(i) + }) + .ToArray(); + } + + private void AttachGenreItems(BaseItemDto dto, BaseItem item) + { + dto.GenreItems = item.Genres + .Where(i => !string.IsNullOrEmpty(i)) + .Select(i => new NameGuidPair + { + Name = i, + Id = GetGenreId(i, item) + }) + .ToArray(); + } + + private Guid GetGenreId(string name, BaseItem owner) + { + if (owner is IHasMusicGenres) + { + return _libraryManager.GetMusicGenreId(name); + } + + return _libraryManager.GetGenreId(name); + } + + private string? GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ImageType imageType, int imageIndex = 0) + { + var image = item.GetImageInfo(imageType, imageIndex); + if (image is not null) + { + return GetTagAndFillBlurhash(dto, item, image); + } + + return null; + } + + private string? GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ItemImageInfo image) + { + var tag = GetImageCacheTag(item, image); + if (tag is null) + { + return null; + } + + if (!string.IsNullOrEmpty(image.BlurHash)) + { + dto.ImageBlurHashes ??= []; + + if (!dto.ImageBlurHashes.TryGetValue(image.Type, out var value)) + { + value = []; + dto.ImageBlurHashes[image.Type] = value; + } + + value[tag] = image.BlurHash; + } + + return tag; + } + + private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, int limit) + { + return GetTagsAndFillBlurhashes(dto, item, imageType, item.GetImages(imageType).Take(limit).ToList()); + } + + private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, List<ItemImageInfo> images) + { + var tags = GetImageTags(item, images); + var hashes = new Dictionary<string, string>(); + for (int i = 0; i < images.Count; i++) + { + var img = images[i]; + if (!string.IsNullOrEmpty(img.BlurHash)) + { + var tag = tags[i]; + hashes[tag] = img.BlurHash; + } + } + + if (hashes.Count > 0) + { + dto.ImageBlurHashes ??= []; + + dto.ImageBlurHashes[imageType] = hashes; + } + + return tags; + } + + /// <summary> + /// Sets simple property values on a DTOBaseItem. + /// </summary> + /// <param name="dto">The dto.</param> + /// <param name="item">The item.</param> + /// <param name="owner">The owner.</param> + /// <param name="options">The options.</param> + private void AttachBasicFields(BaseItemDto dto, BaseItem item, BaseItem? owner, DtoOptions options) + { + if (options.ContainsField(ItemFields.DateCreated)) + { + dto.DateCreated = item.DateCreated; + } + + if (options.ContainsField(ItemFields.Settings)) + { + dto.LockedFields = item.LockedFields; + dto.LockData = item.IsLocked; + dto.ForcedSortName = item.ForcedSortName; + } + + dto.Container = item.Container; + dto.EndDate = item.EndDate; + + if (options.ContainsField(ItemFields.ExternalUrls)) + { + dto.ExternalUrls = _providerManager.GetExternalUrls(item).ToArray(); + } + + if (options.ContainsField(ItemFields.Tags)) + { + dto.Tags = item.Tags; + } + + if (item is IHasAspectRatio hasAspectRatio) + { + dto.AspectRatio = hasAspectRatio.AspectRatio; + } + + dto.ImageBlurHashes = []; + + var backdropLimit = options.GetImageLimit(ImageType.Backdrop); + if (backdropLimit > 0) + { + dto.BackdropImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Backdrop, backdropLimit); + } + + if (options.ContainsField(ItemFields.Genres)) + { + dto.Genres = item.Genres; + AttachGenreItems(dto, item); + } + + if (options.EnableImages) + { + dto.ImageTags = []; + + // Prevent implicitly captured closure + var currentItem = item; + foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type))) + { + if (options.GetImageLimit(image.Type) > 0) + { + var tag = GetTagAndFillBlurhash(dto, item, image); + + if (tag is not null) + { + dto.ImageTags[image.Type] = tag; + } + } + } + } + + dto.Id = item.Id; + dto.IndexNumber = item.IndexNumber; + dto.ParentIndexNumber = item.ParentIndexNumber; + + if (item.IsFolder) + { + dto.IsFolder = true; + } + else if (item is IHasMediaSources) + { + dto.IsFolder = false; + } + + dto.MediaType = item.MediaType; + + if (item is not LiveTvProgram) + { + dto.LocationType = item.LocationType; + } + + dto.Audio = item.Audio; + + if (options.ContainsField(ItemFields.Settings)) + { + dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode; + dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage; + } + + dto.CriticRating = item.CriticRating; + + if (item is IHasDisplayOrder hasDisplayOrder) + { + dto.DisplayOrder = hasDisplayOrder.DisplayOrder; + } + + if (item is IHasCollectionType hasCollectionType) + { + dto.CollectionType = hasCollectionType.CollectionType; + } + + if (options.ContainsField(ItemFields.RemoteTrailers)) + { + dto.RemoteTrailers = item.RemoteTrailers; + } + + dto.Name = item.Name; + dto.OfficialRating = item.OfficialRating; + + if (options.ContainsField(ItemFields.Overview)) + { + dto.Overview = item.Overview; + } + + if (options.ContainsField(ItemFields.OriginalTitle)) + { + dto.OriginalTitle = item.OriginalTitle; + } + + if (options.ContainsField(ItemFields.ParentId)) + { + dto.ParentId = item.DisplayParentId; + } + + AddInheritedImages(dto, item, options, owner); + + if (options.ContainsField(ItemFields.Path)) + { + dto.Path = GetMappedPath(item, owner); + } + + if (options.ContainsField(ItemFields.EnableMediaSourceDisplay)) + { + dto.EnableMediaSourceDisplay = item.EnableMediaSourceDisplay; + } + + dto.PremiereDate = item.PremiereDate; + dto.ProductionYear = item.ProductionYear; + + if (options.ContainsField(ItemFields.ProviderIds)) + { + dto.ProviderIds = item.ProviderIds; + } + + dto.RunTimeTicks = item.RunTimeTicks; + + if (options.ContainsField(ItemFields.SortName)) + { + dto.SortName = item.SortName; + } + + if (options.ContainsField(ItemFields.CustomRating)) + { + dto.CustomRating = item.CustomRating; + } + + if (options.ContainsField(ItemFields.Taglines)) + { + if (!string.IsNullOrEmpty(item.Tagline)) + { + dto.Taglines = new string[] { item.Tagline }; + } + + dto.Taglines ??= Array.Empty<string>(); + } + + dto.Type = item.GetBaseItemKind(); + if ((item.CommunityRating ?? 0) > 0) + { + dto.CommunityRating = item.CommunityRating; + } + + if (item is ISupportsPlaceHolders supportsPlaceHolders && supportsPlaceHolders.IsPlaceHolder) + { + dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder; + } + + if (item.LUFS.HasValue) + { + // -18 LUFS reference, same as ReplayGain 2.0, compatible with ReplayGain 1.0 + dto.NormalizationGain = -18f - item.LUFS; + } + else if (item.NormalizationGain.HasValue) + { + dto.NormalizationGain = item.NormalizationGain; + } + + // Add audio info + if (item is Audio audio) + { + dto.Album = audio.Album; + dto.ExtraType = audio.ExtraType; + + var albumParent = audio.AlbumEntity; + + if (albumParent is not null) + { + dto.AlbumId = albumParent.Id; + dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary); + } + + // if (options.ContainsField(ItemFields.MediaSourceCount)) + // { + // Songs always have one + // } + } + + if (item is IHasArtist hasArtist) + { + dto.Artists = hasArtist.Artists; + + // var artistItems = _libraryManager.GetArtists(new InternalItemsQuery + // { + // EnableTotalRecordCount = false, + // ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) } + // }); + + // dto.ArtistItems = artistItems.Items + // .Select(i => + // { + // var artist = i.Item1; + // return new NameIdPair + // { + // Name = artist.Name, + // Id = artist.Id.ToString("N", CultureInfo.InvariantCulture) + // }; + // }) + // .ToList(); + + // Include artists that are not in the database yet, e.g., just added via metadata editor + // var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList(); + var artistsLookup = _libraryManager.GetArtists([.. hasArtist.Artists.Where(e => !string.IsNullOrWhiteSpace(e))]); + + dto.ArtistItems = hasArtist.Artists + .Where(name => !string.IsNullOrWhiteSpace(name)) + .Distinct() + .Select(name => artistsLookup.TryGetValue(name, out var artists) && artists.Length > 0 + ? new NameGuidPair { Name = name, Id = artists[0].Id } + : null) + .Where(item => item is not null) + .ToArray(); + } + + if (item is IHasAlbumArtist hasAlbumArtist) + { + dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); + + // var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery + // { + // EnableTotalRecordCount = false, + // ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) } + // }); + + // dto.AlbumArtists = artistItems.Items + // .Select(i => + // { + // var artist = i.Item1; + // return new NameIdPair + // { + // Name = artist.Name, + // Id = artist.Id.ToString("N", CultureInfo.InvariantCulture) + // }; + // }) + // .ToList(); + + var albumArtistsLookup = _libraryManager.GetArtists([.. hasAlbumArtist.AlbumArtists.Where(e => !string.IsNullOrWhiteSpace(e))]); + + dto.AlbumArtists = hasAlbumArtist.AlbumArtists + .Where(name => !string.IsNullOrWhiteSpace(name)) + .Distinct() + .Select(name => albumArtistsLookup.TryGetValue(name, out var albumArtists) && albumArtists.Length > 0 + ? new NameGuidPair { Name = name, Id = albumArtists[0].Id } + : null) + .Where(item => item is not null) + .ToArray(); + } + + // Add video info + if (item is Video video) + { + dto.VideoType = video.VideoType; + dto.Video3DFormat = video.Video3DFormat; + dto.IsoType = video.IsoType; + + if (video.HasSubtitles) + { + dto.HasSubtitles = video.HasSubtitles; + } + + if (video.AdditionalParts.Length != 0) + { + dto.PartCount = video.AdditionalParts.Length + 1; + } + + if (options.ContainsField(ItemFields.MediaSourceCount)) + { + var mediaSourceCount = video.MediaSourceCount; + if (mediaSourceCount != 1) + { + dto.MediaSourceCount = mediaSourceCount; + } + } + + if (options.ContainsField(ItemFields.Chapters)) + { + dto.Chapters = _chapterManager.GetChapters(item.Id).ToList(); + } + + if (options.ContainsField(ItemFields.Trickplay)) + { + var trickplay = _trickplayManager.GetTrickplayManifest(item).GetAwaiter().GetResult(); + dto.Trickplay = trickplay.ToDictionary( + mediaStream => mediaStream.Key, + mediaStream => mediaStream.Value.ToDictionary( + width => width.Key, + width => new TrickplayInfoDto(width.Value))); + } + + dto.ExtraType = video.ExtraType; + } + + if (options.ContainsField(ItemFields.MediaStreams)) + { + // Add VideoInfo + if (item is IHasMediaSources) + { + MediaStream[] mediaStreams; + + if (dto.MediaSources is not null && dto.MediaSources.Length > 0) + { + if (item.SourceType == SourceType.Channel) + { + mediaStreams = dto.MediaSources[0].MediaStreams.ToArray(); + } + else + { + string id = item.Id.ToString("N", CultureInfo.InvariantCulture); + mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)) + .SelectMany(i => i.MediaStreams) + .ToArray(); + } + } + else + { + mediaStreams = _mediaSourceManager.GetStaticMediaSources(item, true)[0].MediaStreams.ToArray(); + } + + dto.MediaStreams = mediaStreams; + } + } + + BaseItem[]? allExtras = null; + + if (options.ContainsField(ItemFields.SpecialFeatureCount)) + { + allExtras = item.GetExtras().ToArray(); + dto.SpecialFeatureCount = allExtras.Count(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contains(i.ExtraType.Value)); + } + + if (options.ContainsField(ItemFields.LocalTrailerCount)) + { + if (item is IHasTrailers hasTrailers) + { + dto.LocalTrailerCount = hasTrailers.LocalTrailers.Count; + } + else + { + dto.LocalTrailerCount = (allExtras ?? item.GetExtras()).Count(i => i.ExtraType == ExtraType.Trailer); + } + } + + // Add EpisodeInfo + if (item is Episode episode) + { + dto.IndexNumberEnd = episode.IndexNumberEnd; + dto.SeriesName = episode.SeriesName; + + if (options.ContainsField(ItemFields.SpecialEpisodeNumbers)) + { + dto.AirsAfterSeasonNumber = episode.AirsAfterSeasonNumber; + dto.AirsBeforeEpisodeNumber = episode.AirsBeforeEpisodeNumber; + dto.AirsBeforeSeasonNumber = episode.AirsBeforeSeasonNumber; + } + + dto.SeasonName = episode.SeasonName; + dto.SeasonId = episode.SeasonId; + dto.SeriesId = episode.SeriesId; + + Series? episodeSeries = null; + + // this block will add the series poster for episodes without a poster + // TODO maybe remove the if statement entirely + // if (options.ContainsField(ItemFields.SeriesPrimaryImage)) + { + episodeSeries ??= episode.Series; + if (episodeSeries is not null) + { + dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary); + if (dto.ImageTags is null || !dto.ImageTags.ContainsKey(ImageType.Primary)) + { + AttachPrimaryImageAspectRatio(dto, episodeSeries); + } + } + } + + if (options.ContainsField(ItemFields.SeriesStudio)) + { + episodeSeries ??= episode.Series; + if (episodeSeries is not null) + { + dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault(); + } + } + } + + // Add SeriesInfo + Series? series; + if (item is Series tmp) + { + series = tmp; + dto.AirDays = series.AirDays; + dto.AirTime = series.AirTime; + dto.Status = series.Status?.ToString(); + } + + // Add SeasonInfo + if (item is Season season) + { + dto.SeriesName = season.SeriesName; + dto.SeriesId = season.SeriesId; + + series = null; + + if (options.ContainsField(ItemFields.SeriesStudio)) + { + series ??= season.Series; + if (series is not null) + { + dto.SeriesStudio = series.Studios.FirstOrDefault(); + } + } + + // this block will add the series poster for seasons without a poster + // TODO maybe remove the if statement entirely + // if (options.ContainsField(ItemFields.SeriesPrimaryImage)) + { + series ??= season.Series; + if (series is not null) + { + dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary); + if (dto.ImageTags is null || !dto.ImageTags.ContainsKey(ImageType.Primary)) + { + AttachPrimaryImageAspectRatio(dto, series); + } + } + } + } + + if (item is MusicVideo musicVideo) + { + SetMusicVideoProperties(dto, musicVideo); + } + + if (item is Book book) + { + SetBookProperties(dto, book); + } + + if (options.ContainsField(ItemFields.ProductionLocations)) + { + if (item.ProductionLocations.Length > 0 || item is Movie) + { + dto.ProductionLocations = item.ProductionLocations; + } + } + + if (options.ContainsField(ItemFields.Width)) + { + var width = item.Width; + if (width > 0) + { + dto.Width = width; + } + } + + if (options.ContainsField(ItemFields.Height)) + { + var height = item.Height; + if (height > 0) + { + dto.Height = height; + } + } + + if (options.ContainsField(ItemFields.IsHD)) + { + // Compatibility + if (item.IsHD) + { + dto.IsHD = true; + } + } + + if (item is Photo photo) + { + SetPhotoProperties(dto, photo); + } + + dto.ChannelId = item.ChannelId; + + if (item.SourceType == SourceType.Channel) + { + var channel = _libraryManager.GetItemById(item.ChannelId); + if (channel is not null) + { + dto.ChannelName = channel.Name; + } + } + } + + private BaseItem? GetImageDisplayParent(BaseItem currentItem, BaseItem originalItem) + { + if (currentItem is MusicAlbum musicAlbum) + { + var artist = musicAlbum.GetMusicArtist(new DtoOptions(false)); + if (artist is not null) + { + return artist; + } + } + + var parent = currentItem.DisplayParent ?? currentItem.GetOwner() ?? currentItem.GetParent(); + + if (parent is null && originalItem is not UserRootFolder && originalItem is not UserView && originalItem is not AggregateFolder && originalItem is not ICollectionFolder && originalItem is not Channel) + { + parent = _libraryManager.GetCollectionFolders(originalItem).FirstOrDefault(); + } + + return parent; + } + + private void AddInheritedImages(BaseItemDto dto, BaseItem item, DtoOptions options, BaseItem? owner) + { + if (!item.SupportsInheritedParentImages) + { + return; + } + + var logoLimit = options.GetImageLimit(ImageType.Logo); + var artLimit = options.GetImageLimit(ImageType.Art); + var thumbLimit = options.GetImageLimit(ImageType.Thumb); + var backdropLimit = options.GetImageLimit(ImageType.Backdrop); + + // For now. Emby apps are not using this + artLimit = 0; + + if (logoLimit == 0 && artLimit == 0 && thumbLimit == 0 && backdropLimit == 0) + { + return; + } + + BaseItem? parent = null; + var isFirst = true; + + var imageTags = dto.ImageTags; + + while ((!(imageTags is not null && imageTags.ContainsKey(ImageType.Logo)) && logoLimit > 0) + || (!(imageTags is not null && imageTags.ContainsKey(ImageType.Art)) && artLimit > 0) + || (!(imageTags is not null && imageTags.ContainsKey(ImageType.Thumb)) && thumbLimit > 0) + || parent is Series) + { + parent ??= isFirst ? GetImageDisplayParent(item, item) ?? owner : parent; + if (parent is null) + { + break; + } + + var allImages = parent.ImageInfos; + + if (logoLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Logo)) && dto.ParentLogoItemId is null) + { + var image = allImages.FirstOrDefault(i => i.Type == ImageType.Logo); + + if (image is not null) + { + dto.ParentLogoItemId = parent.Id; + dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image); + } + } + + if (artLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtItemId is null) + { + var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art); + + if (image is not null) + { + dto.ParentArtItemId = parent.Id; + dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image); + } + } + + if (thumbLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId is null || parent is Series) && parent is not ICollectionFolder && parent is not UserView) + { + var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb); + + if (image is not null) + { + dto.ParentThumbItemId = parent.Id; + dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image); + } + } + + if (backdropLimit > 0 && !((dto.BackdropImageTags is not null && dto.BackdropImageTags.Length > 0) || (dto.ParentBackdropImageTags is not null && dto.ParentBackdropImageTags.Length > 0))) + { + var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList(); + + if (images.Count > 0) + { + dto.ParentBackdropItemId = parent.Id; + dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images); + } + } + + isFirst = false; + + if (!parent.SupportsInheritedParentImages) + { + break; + } + + parent = GetImageDisplayParent(parent, item); + } + } + + private string GetMappedPath(BaseItem item, BaseItem? ownerItem) + { + var path = item.Path; + + if (item.IsFileProtocol) + { + path = _libraryManager.GetPathAfterNetworkSubstitution(path, ownerItem ?? item); + } + + return path; + } + + /// <summary> + /// Attaches the primary image aspect ratio. + /// </summary> + /// <param name="dto">The dto.</param> + /// <param name="item">The item.</param> + public void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item) + { + dto.PrimaryImageAspectRatio = GetPrimaryImageAspectRatio(item); + } + + public double? GetPrimaryImageAspectRatio(BaseItem item) + { + var imageInfo = item.GetImageInfo(ImageType.Primary, 0); + + if (imageInfo is null) + { + return null; + } + + if (!imageInfo.IsLocalFile) + { + return item.GetDefaultPrimaryImageAspectRatio(); + } + + try + { + var size = _imageProcessor.GetImageDimensions(item, imageInfo); + var width = size.Width; + var height = size.Height; + if (width > 0 && height > 0) + { + return (double)width / height; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to determine primary image aspect ratio for {ImagePath}", imageInfo.Path); + } + + return item.GetDefaultPrimaryImageAspectRatio(); + } + } +} diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj new file mode 100644 index 00000000..f312fb4d --- /dev/null +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -0,0 +1,76 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <!-- ProjectGuid is only included as a requirement for SonarQube analysis --> + <PropertyGroup> + <ProjectGuid>{E383961B-9356-4D5D-8233-9A1079D03055}</ProjectGuid> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" /> + <ProjectReference Include="..\Jellyfin.Api\Jellyfin.Api.csproj" /> + <ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" /> + <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> + <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" /> + <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> + <ProjectReference Include="..\MediaBrowser.Providers\MediaBrowser.Providers.csproj" /> + <ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj" /> + <ProjectReference Include="..\MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj" /> + <ProjectReference Include="..\Emby.Photos\Emby.Photos.csproj" /> + <ProjectReference Include="..\src\Jellyfin.Drawing\Jellyfin.Drawing.csproj" /> + <ProjectReference Include="..\MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj" /> + <ProjectReference Include="..\src\Jellyfin.Database\Jellyfin.Database.Implementations\Jellyfin.Database.Implementations.csproj" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="BitFaster.Caching" /> + <PackageReference Include="DiscUtils.Udf" /> + <PackageReference Include="Microsoft.Data.Sqlite" /> + <PackageReference Include="Microsoft.Extensions.DependencyInjection" /> + <PackageReference Include="Microsoft.Extensions.Caching.Memory" /> + <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" /> + <PackageReference Include="prometheus-net.DotNetRuntime" /> + <PackageReference Include="DotNet.Glob" /> + </ItemGroup> + + <ItemGroup> + <Compile Include="..\SharedVersion.cs" /> + </ItemGroup> + + <PropertyGroup> + <TargetFramework>net10.0</TargetFramework> + <GenerateAssemblyInfo>false</GenerateAssemblyInfo> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + </PropertyGroup> + + <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> + <CodeAnalysisTreatWarningsAsErrors>false</CodeAnalysisTreatWarningsAsErrors> + </PropertyGroup> + + <!-- Code Analyzers --> + <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <!-- TODO: Add IDisposableAnalyzers --> + <!-- <PackageReference Include="IDisposableAnalyzers"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> --> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> + <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="Ignore" /> + </ItemGroup> + + <ItemGroup> + <EmbeddedResource Include="Localization\iso6392.txt" /> + <EmbeddedResource Include="Localization\countries.json" /> + <EmbeddedResource Include="Localization\Core\*.json" /> + <EmbeddedResource Include="Localization\Ratings\*.json" /> + </ItemGroup> +</Project> diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs new file mode 100644 index 00000000..933cfc8c --- /dev/null +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -0,0 +1,402 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Events; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Session; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.EntryPoints; + +/// <summary> +/// A <see cref="IHostedService"/> responsible for notifying users when libraries are updated. +/// </summary> +public sealed class LibraryChangedNotifier : IHostedService, IDisposable +{ + private readonly ILibraryManager _libraryManager; + private readonly IServerConfigurationManager _configurationManager; + private readonly IProviderManager _providerManager; + private readonly ISessionManager _sessionManager; + private readonly IUserManager _userManager; + private readonly ILogger<LibraryChangedNotifier> _logger; + + private readonly Lock _libraryChangedSyncLock = new(); + private readonly List<Folder> _foldersAddedTo = new(); + private readonly List<Folder> _foldersRemovedFrom = new(); + private readonly List<BaseItem> _itemsAdded = new(); + private readonly List<BaseItem> _itemsRemoved = new(); + private readonly List<BaseItem> _itemsUpdated = new(); + private readonly ConcurrentDictionary<Guid, DateTime> _lastProgressMessageTimes = new(); + + private Timer? _libraryUpdateTimer; + + /// <summary> + /// Initializes a new instance of the <see cref="LibraryChangedNotifier"/> class. + /// </summary> + /// <param name="libraryManager">The <see cref="ILibraryManager"/>.</param> + /// <param name="configurationManager">The <see cref="IServerConfigurationManager"/>.</param> + /// <param name="sessionManager">The <see cref="ISessionManager"/>.</param> + /// <param name="userManager">The <see cref="IUserManager"/>.</param> + /// <param name="logger">The <see cref="ILogger"/>.</param> + /// <param name="providerManager">The <see cref="IProviderManager"/>.</param> + public LibraryChangedNotifier( + ILibraryManager libraryManager, + IServerConfigurationManager configurationManager, + ISessionManager sessionManager, + IUserManager userManager, + ILogger<LibraryChangedNotifier> logger, + IProviderManager providerManager) + { + _libraryManager = libraryManager; + _configurationManager = configurationManager; + _sessionManager = sessionManager; + _userManager = userManager; + _logger = logger; + _providerManager = providerManager; + } + + /// <inheritdoc /> + public Task StartAsync(CancellationToken cancellationToken) + { + _libraryManager.ItemAdded += OnLibraryItemAdded; + _libraryManager.ItemUpdated += OnLibraryItemUpdated; + _libraryManager.ItemRemoved += OnLibraryItemRemoved; + + _providerManager.RefreshCompleted += OnProviderRefreshCompleted; + _providerManager.RefreshStarted += OnProviderRefreshStarted; + _providerManager.RefreshProgress += OnProviderRefreshProgress; + + return Task.CompletedTask; + } + + /// <inheritdoc /> + public Task StopAsync(CancellationToken cancellationToken) + { + _libraryManager.ItemAdded -= OnLibraryItemAdded; + _libraryManager.ItemUpdated -= OnLibraryItemUpdated; + _libraryManager.ItemRemoved -= OnLibraryItemRemoved; + + _providerManager.RefreshCompleted -= OnProviderRefreshCompleted; + _providerManager.RefreshStarted -= OnProviderRefreshStarted; + _providerManager.RefreshProgress -= OnProviderRefreshProgress; + + return Task.CompletedTask; + } + + private void OnProviderRefreshProgress(object? sender, GenericEventArgs<Tuple<BaseItem, double>> e) + { + var item = e.Argument.Item1; + + if (!EnableRefreshMessage(item)) + { + return; + } + + var progress = e.Argument.Item2; + + if (_lastProgressMessageTimes.TryGetValue(item.Id, out var lastMessageSendTime)) + { + if (progress > 0 && progress < 100 && (DateTime.UtcNow - lastMessageSendTime).TotalMilliseconds < 1000) + { + return; + } + } + + _lastProgressMessageTimes.AddOrUpdate(item.Id, _ => DateTime.UtcNow, (_, _) => DateTime.UtcNow); + + var dict = new Dictionary<string, string>(); + dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture); + dict["Progress"] = progress.ToString(CultureInfo.InvariantCulture); + + try + { + _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, dict, CancellationToken.None); + } + catch + { + } + + var collectionFolders = _libraryManager.GetCollectionFolders(item); + + foreach (var collectionFolder in collectionFolders) + { + var collectionFolderDict = new Dictionary<string, string> + { + ["ItemId"] = collectionFolder.Id.ToString("N", CultureInfo.InvariantCulture), + ["Progress"] = (collectionFolder.GetRefreshProgress() ?? 0).ToString(CultureInfo.InvariantCulture) + }; + + try + { + _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, collectionFolderDict, CancellationToken.None); + } + catch + { + } + } + } + + private void OnProviderRefreshStarted(object? sender, GenericEventArgs<BaseItem> e) + => OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 0))); + + private void OnProviderRefreshCompleted(object? sender, GenericEventArgs<BaseItem> e) + { + OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 100))); + + _lastProgressMessageTimes.TryRemove(e.Argument.Id, out _); + } + + private static bool EnableRefreshMessage(BaseItem item) + => item is Folder { IsRoot: false, IsTopParent: true } + and not (AggregateFolder or UserRootFolder or UserView or Channel); + + private void OnLibraryItemAdded(object? sender, ItemChangeEventArgs e) + => OnLibraryChange(e.Item, e.Parent, _itemsAdded, _foldersAddedTo); + + private void OnLibraryItemUpdated(object? sender, ItemChangeEventArgs e) + => OnLibraryChange(e.Item, e.Parent, _itemsUpdated, null); + + private void OnLibraryItemRemoved(object? sender, ItemChangeEventArgs e) + => OnLibraryChange(e.Item, e.Parent, _itemsRemoved, _foldersRemovedFrom); + + private void OnLibraryChange(BaseItem item, BaseItem parent, List<BaseItem> itemsList, List<Folder>? foldersList) + { + if (!FilterItem(item)) + { + return; + } + + lock (_libraryChangedSyncLock) + { + var updateDuration = TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration); + + if (_libraryUpdateTimer is null) + { + _libraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, updateDuration, Timeout.InfiniteTimeSpan); + } + else + { + _libraryUpdateTimer.Change(updateDuration, Timeout.InfiniteTimeSpan); + } + + if (foldersList is not null && parent is Folder folder) + { + foldersList.Add(folder); + } + + itemsList.Add(item); + } + } + + private async void LibraryUpdateTimerCallback(object? state) + { + List<Folder> foldersAddedTo; + List<Folder> foldersRemovedFrom; + List<BaseItem> itemsUpdated; + List<BaseItem> itemsAdded; + List<BaseItem> itemsRemoved; + lock (_libraryChangedSyncLock) + { + // Remove dupes in case some were saved multiple times + foldersAddedTo = _foldersAddedTo + .DistinctBy(x => x.Id) + .ToList(); + + foldersRemovedFrom = _foldersRemovedFrom + .DistinctBy(x => x.Id) + .ToList(); + + itemsUpdated = _itemsUpdated + .Where(i => !_itemsAdded.Contains(i)) + .DistinctBy(x => x.Id) + .ToList(); + + itemsAdded = _itemsAdded.ToList(); + itemsRemoved = _itemsRemoved.ToList(); + + if (_libraryUpdateTimer is not null) + { + _libraryUpdateTimer.Dispose(); + _libraryUpdateTimer = null; + } + + _itemsAdded.Clear(); + _itemsRemoved.Clear(); + _itemsUpdated.Clear(); + _foldersAddedTo.Clear(); + _foldersRemovedFrom.Clear(); + } + + await SendChangeNotifications(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo, foldersRemovedFrom, CancellationToken.None).ConfigureAwait(false); + } + + private async Task SendChangeNotifications( + List<BaseItem> itemsAdded, + List<BaseItem> itemsUpdated, + List<BaseItem> itemsRemoved, + List<Folder> foldersAddedTo, + List<Folder> foldersRemovedFrom, + CancellationToken cancellationToken) + { + var userIds = _sessionManager.Sessions + .Select(i => i.UserId) + .Where(i => !i.IsEmpty()) + .Distinct() + .ToArray(); + + foreach (var userId in userIds) + { + LibraryUpdateInfo info; + + try + { + info = GetLibraryUpdateInfo(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo, foldersRemovedFrom, userId); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in GetLibraryUpdateInfo"); + return; + } + + if (info.IsEmpty) + { + continue; + } + + try + { + await _sessionManager.SendMessageToUserSessions( + new List<Guid> { userId }, + SessionMessageType.LibraryChanged, + info, + cancellationToken) + .ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error sending LibraryChanged message"); + } + } + } + + private LibraryUpdateInfo GetLibraryUpdateInfo( + List<BaseItem> itemsAdded, + List<BaseItem> itemsUpdated, + List<BaseItem> itemsRemoved, + List<Folder> foldersAddedTo, + List<Folder> foldersRemovedFrom, + Guid userId) + { + var user = _userManager.GetUserById(userId); + ArgumentNullException.ThrowIfNull(user); + + var newAndRemoved = new List<BaseItem>(); + newAndRemoved.AddRange(foldersAddedTo); + newAndRemoved.AddRange(foldersRemovedFrom); + + var allUserRootChildren = _libraryManager.GetUserRootFolder() + .GetChildren(user, true) + .OfType<Folder>() + .ToList(); + + return new LibraryUpdateInfo + { + ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)) + .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) + .Distinct() + .ToArray(), + ItemsUpdated = itemsUpdated.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)) + .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) + .Distinct() + .ToArray(), + ItemsRemoved = itemsRemoved.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, true)) + .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) + .Distinct() + .ToArray(), + FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)) + .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) + .Distinct() + .ToArray(), + FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)) + .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) + .Distinct() + .ToArray(), + CollectionFolders = GetTopParentIds(newAndRemoved, allUserRootChildren).ToArray() + }; + } + + private static bool FilterItem(BaseItem item) + { + if (!item.IsFolder && !item.HasPathProtocol) + { + return false; + } + + if (item is IItemByName && item is not MusicArtist) + { + return false; + } + + return item.SourceType == SourceType.Library; + } + + private static IEnumerable<string> GetTopParentIds(List<BaseItem> items, List<Folder> allUserRootChildren) + { + var list = new List<string>(); + + foreach (var item in items) + { + // If the physical root changed, return the user root + if (item is AggregateFolder) + { + continue; + } + + foreach (var folder in allUserRootChildren) + { + list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture)); + } + } + + return list.Distinct(StringComparer.Ordinal); + } + + private T[] TranslatePhysicalItemToUserLibrary<T>(T item, User user, bool includeIfNotFound = false) + where T : BaseItem + { + // If the physical root changed, return the user root + if (item is AggregateFolder) + { + return _libraryManager.GetUserRootFolder() is T t ? new[] { t } : Array.Empty<T>(); + } + + // Return it only if it's in the user's library + if (includeIfNotFound || item.IsVisibleStandalone(user)) + { + return new[] { item }; + } + + return Array.Empty<T>(); + } + + /// <inheritdoc /> + public void Dispose() + { + _libraryUpdateTimer?.Dispose(); + _libraryUpdateTimer = null; + } +} diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs new file mode 100644 index 00000000..fc174b7c --- /dev/null +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Session; +using Microsoft.Extensions.Hosting; + +namespace Emby.Server.Implementations.EntryPoints +{ + /// <summary> + /// <see cref="IHostedService"/> responsible for notifying users when associated item data is updated. + /// </summary> + public sealed class UserDataChangeNotifier : IHostedService, IDisposable + { + private const int UpdateDuration = 500; + + private readonly ISessionManager _sessionManager; + private readonly IUserDataManager _userDataManager; + private readonly IUserManager _userManager; + + private readonly Dictionary<Guid, List<BaseItem>> _changedItems = new(); + private readonly Lock _syncLock = new(); + + private Timer? _updateTimer; + + /// <summary> + /// Initializes a new instance of the <see cref="UserDataChangeNotifier"/> class. + /// </summary> + /// <param name="userDataManager">The <see cref="IUserDataManager"/>.</param> + /// <param name="sessionManager">The <see cref="ISessionManager"/>.</param> + /// <param name="userManager">The <see cref="IUserManager"/>.</param> + public UserDataChangeNotifier( + IUserDataManager userDataManager, + ISessionManager sessionManager, + IUserManager userManager) + { + _userDataManager = userDataManager; + _sessionManager = sessionManager; + _userManager = userManager; + } + + /// <inheritdoc /> + public Task StartAsync(CancellationToken cancellationToken) + { + _userDataManager.UserDataSaved += OnUserDataManagerUserDataSaved; + + return Task.CompletedTask; + } + + /// <inheritdoc /> + public Task StopAsync(CancellationToken cancellationToken) + { + _userDataManager.UserDataSaved -= OnUserDataManagerUserDataSaved; + + return Task.CompletedTask; + } + + private void OnUserDataManagerUserDataSaved(object? sender, UserDataSaveEventArgs e) + { + if (e.SaveReason == UserDataSaveReason.PlaybackProgress) + { + return; + } + + lock (_syncLock) + { + if (_updateTimer is null) + { + _updateTimer = new Timer( + UpdateTimerCallback, + null, + UpdateDuration, + Timeout.Infinite); + } + else + { + _updateTimer.Change(UpdateDuration, Timeout.Infinite); + } + + if (!_changedItems.TryGetValue(e.UserId, out List<BaseItem>? keys)) + { + keys = new List<BaseItem>(); + _changedItems[e.UserId] = keys; + } + + keys.Add(e.Item); + + var baseItem = e.Item; + + // Go up one level for indicators + if (baseItem is not null) + { + var parent = baseItem.GetOwner() ?? baseItem.GetParent(); + + if (parent is not null) + { + keys.Add(parent); + } + } + } + } + + private async void UpdateTimerCallback(object? state) + { + List<KeyValuePair<Guid, List<BaseItem>>> changes; + lock (_syncLock) + { + // Remove dupes in case some were saved multiple times + changes = _changedItems.ToList(); + _changedItems.Clear(); + + if (_updateTimer is not null) + { + _updateTimer.Dispose(); + _updateTimer = null; + } + } + + foreach (var (userId, changedItems) in changes) + { + await _sessionManager.SendMessageToUserSessions( + [userId], + SessionMessageType.UserDataChanged, + () => GetUserDataChangeInfo(userId, changedItems), + default).ConfigureAwait(false); + } + } + + private UserDataChangeInfo GetUserDataChangeInfo(Guid userId, List<BaseItem> changedItems) + { + var user = _userManager.GetUserById(userId) + ?? throw new ArgumentException("Invalid user ID", nameof(userId)); + + return new UserDataChangeInfo + { + UserId = userId, + UserDataList = changedItems + .DistinctBy(x => x.Id) + .Select(i => + { + var dto = _userDataManager.GetUserDataDto(i, user); + if (dto is null) + { + return null!; + } + + dto.ItemId = i.Id; + return dto; + }) + .Where(e => e is not null) + .ToArray() + }; + } + + /// <inheritdoc /> + public void Dispose() + { + _updateTimer?.Dispose(); + _updateTimer = null; + } + } +} diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs new file mode 100644 index 00000000..8a79cdeb --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -0,0 +1,43 @@ +#pragma warning disable CS1591 + +using System.Threading.Tasks; +using Jellyfin.Data; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Http; + +namespace Emby.Server.Implementations.HttpServer.Security +{ + public class AuthService : IAuthService + { + private readonly IAuthorizationContext _authorizationContext; + + public AuthService( + IAuthorizationContext authorizationContext) + { + _authorizationContext = authorizationContext; + } + + public async Task<AuthorizationInfo> Authenticate(HttpRequest request) + { + var auth = await _authorizationContext.GetAuthorizationInfo(request).ConfigureAwait(false); + + if (!auth.HasToken) + { + return auth; + } + + if (!auth.IsAuthenticated) + { + throw new SecurityException("Invalid token."); + } + + if (auth.User?.HasPermission(PermissionKind.IsDisabled) ?? false) + { + throw new SecurityException("User account has been disabled."); + } + + return auth; + } + } +} diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs new file mode 100644 index 00000000..373b0994 --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -0,0 +1,283 @@ +using System; +using System.Buffers; +using System.IO.Pipelines; +using System.Net; +using System.Net.WebSockets; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Extensions.Json; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Net.WebSocketMessages; +using MediaBrowser.Controller.Net.WebSocketMessages.Outbound; +using MediaBrowser.Model.Session; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.HttpServer +{ + /// <summary> + /// Class WebSocketConnection. + /// </summary> + public class WebSocketConnection : IWebSocketConnection + { + /// <summary> + /// The logger. + /// </summary> + private readonly ILogger<WebSocketConnection> _logger; + + /// <summary> + /// The json serializer options. + /// </summary> + private readonly JsonSerializerOptions _jsonOptions; + + /// <summary> + /// The socket. + /// </summary> + private readonly WebSocket _socket; + + private bool _disposed = false; + + /// <summary> + /// Initializes a new instance of the <see cref="WebSocketConnection" /> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="socket">The socket.</param> + /// <param name="authorizationInfo">The authorization information.</param> + /// <param name="remoteEndPoint">The remote end point.</param> + public WebSocketConnection( + ILogger<WebSocketConnection> logger, + WebSocket socket, + AuthorizationInfo authorizationInfo, + IPAddress? remoteEndPoint) + { + _logger = logger; + _socket = socket; + AuthorizationInfo = authorizationInfo; + RemoteEndPoint = remoteEndPoint; + + _jsonOptions = JsonDefaults.Options; + LastActivityDate = DateTime.UtcNow; + } + + /// <inheritdoc /> + public event EventHandler<EventArgs>? Closed; + + /// <inheritdoc /> + public AuthorizationInfo AuthorizationInfo { get; } + + /// <inheritdoc /> + public IPAddress? RemoteEndPoint { get; } + + /// <inheritdoc /> + public Func<WebSocketMessageInfo, Task>? OnReceive { get; set; } + + /// <inheritdoc /> + public DateTime LastActivityDate { get; private set; } + + /// <inheritdoc /> + public DateTime LastKeepAliveDate { get; set; } + + /// <inheritdoc /> + public WebSocketState State => _socket.State; + + /// <inheritdoc /> + public async Task SendAsync(OutboundWebSocketMessage message, CancellationToken cancellationToken) + { + var json = JsonSerializer.SerializeToUtf8Bytes(message, _jsonOptions); + await _socket.SendAsync(json, WebSocketMessageType.Text, true, cancellationToken).ConfigureAwait(false); + } + + /// <inheritdoc /> + public async Task SendAsync<T>(OutboundWebSocketMessage<T> message, CancellationToken cancellationToken) + { + var json = JsonSerializer.SerializeToUtf8Bytes(message, _jsonOptions); + await _socket.SendAsync(json, WebSocketMessageType.Text, true, cancellationToken).ConfigureAwait(false); + } + + /// <inheritdoc /> + public async Task ReceiveAsync(CancellationToken cancellationToken = default) + { + var pipe = new Pipe(); + var writer = pipe.Writer; + + ValueWebSocketReceiveResult receiveResult; + do + { + // Allocate at least 512 bytes from the PipeWriter + Memory<byte> memory = writer.GetMemory(512); + try + { + receiveResult = await _socket.ReceiveAsync(memory, cancellationToken).ConfigureAwait(false); + } + catch (WebSocketException ex) + { + _logger.LogWarning("WS {IP} error receiving data: {Message}", RemoteEndPoint, ex.Message); + break; + } + + int bytesRead = receiveResult.Count; + if (bytesRead == 0) + { + break; + } + + // Tell the PipeWriter how much was read from the Socket + writer.Advance(bytesRead); + + // Make the data available to the PipeReader + FlushResult flushResult = await writer.FlushAsync(cancellationToken).ConfigureAwait(false); + if (flushResult.IsCompleted) + { + // The PipeReader stopped reading + break; + } + + LastActivityDate = DateTime.UtcNow; + + if (receiveResult.EndOfMessage) + { + await ProcessInternal(pipe.Reader).ConfigureAwait(false); + } + } + while ((_socket.State == WebSocketState.Open || _socket.State == WebSocketState.Connecting) + && receiveResult.MessageType != WebSocketMessageType.Close); + + Closed?.Invoke(this, EventArgs.Empty); + + if (_socket.State == WebSocketState.Open + || _socket.State == WebSocketState.CloseReceived + || _socket.State == WebSocketState.CloseSent) + { + await _socket.CloseAsync( + WebSocketCloseStatus.NormalClosure, + string.Empty, + cancellationToken).ConfigureAwait(false); + } + } + + private async Task ProcessInternal(PipeReader reader) + { + ReadResult result = await reader.ReadAsync().ConfigureAwait(false); + ReadOnlySequence<byte> buffer = result.Buffer; + + if (OnReceive is null) + { + // Tell the PipeReader how much of the buffer we have consumed + reader.AdvanceTo(buffer.End); + return; + } + + InboundWebSocketMessage<object>? stub; + long bytesConsumed; + try + { + stub = DeserializeWebSocketMessage(buffer, out bytesConsumed); + } + catch (JsonException ex) + { + // Tell the PipeReader how much of the buffer we have consumed + reader.AdvanceTo(buffer.End); + _logger.LogError(ex, "Error processing web socket message: {Data}", Encoding.UTF8.GetString(buffer)); + return; + } + + if (stub is null) + { + _logger.LogError("Error processing web socket message"); + return; + } + + // Tell the PipeReader how much of the buffer we have consumed + reader.AdvanceTo(buffer.GetPosition(bytesConsumed)); + + _logger.LogDebug("WS {IP} received message: {@Message}", RemoteEndPoint, stub); + + if (stub.MessageType == SessionMessageType.KeepAlive) + { + await SendKeepAliveResponse().ConfigureAwait(false); + } + else + { + try + { + await OnReceive( + new WebSocketMessageInfo + { + MessageType = stub.MessageType, + Data = stub.Data?.ToString(), // Data can be null + Connection = this + }).ConfigureAwait(false); + } + catch (Exception exception) + { + _logger.LogWarning(exception, "Failed to process WebSocket message"); + } + } + } + + internal InboundWebSocketMessage<object>? DeserializeWebSocketMessage(ReadOnlySequence<byte> bytes, out long bytesConsumed) + { + var jsonReader = new Utf8JsonReader(bytes); + var ret = JsonSerializer.Deserialize<InboundWebSocketMessage<object>>(ref jsonReader, _jsonOptions); + bytesConsumed = jsonReader.BytesConsumed; + return ret; + } + + private async Task SendKeepAliveResponse() + { + LastKeepAliveDate = DateTime.UtcNow; + await SendAsync( + new OutboundKeepAliveMessage(), + CancellationToken.None).ConfigureAwait(false); + } + + /// <inheritdoc /> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> + /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool dispose) + { + if (_disposed) + { + return; + } + + if (dispose) + { + _socket.Dispose(); + } + + _disposed = true; + } + + /// <inheritdoc /> + public async ValueTask DisposeAsync() + { + await DisposeAsyncCore().ConfigureAwait(false); + Dispose(false); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Used to perform asynchronous cleanup of managed resources or for cascading calls to <see cref="DisposeAsync"/>. + /// </summary> + /// <returns>A ValueTask.</returns> + protected virtual async ValueTask DisposeAsyncCore() + { + if (_socket.State == WebSocketState.Open) + { + await _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "System Shutdown", CancellationToken.None).ConfigureAwait(false); + } + + _socket.Dispose(); + } + } +} diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs new file mode 100644 index 00000000..cb5b3993 --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs @@ -0,0 +1,98 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.WebSockets; +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.HttpServer +{ + public class WebSocketManager : IWebSocketManager + { + private readonly IWebSocketListener[] _webSocketListeners; + private readonly IAuthService _authService; + private readonly ILogger<WebSocketManager> _logger; + private readonly ILoggerFactory _loggerFactory; + + public WebSocketManager( + IAuthService authService, + IEnumerable<IWebSocketListener> webSocketListeners, + ILogger<WebSocketManager> logger, + ILoggerFactory loggerFactory) + { + _webSocketListeners = webSocketListeners.ToArray(); + _authService = authService; + _logger = logger; + _loggerFactory = loggerFactory; + } + + /// <inheritdoc /> + public async Task WebSocketRequestHandler(HttpContext context) + { + var authorizationInfo = await _authService.Authenticate(context.Request).ConfigureAwait(false); + if (!authorizationInfo.IsAuthenticated) + { + throw new SecurityException("Token is required"); + } + + try + { + _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress); + + WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); + + var connection = new WebSocketConnection( + _loggerFactory.CreateLogger<WebSocketConnection>(), + webSocket, + authorizationInfo, + context.GetNormalizedRemoteIP()) + { + OnReceive = ProcessWebSocketMessageReceived + }; + await using (connection.ConfigureAwait(false)) + { + var tasks = new Task[_webSocketListeners.Length]; + for (var i = 0; i < _webSocketListeners.Length; ++i) + { + tasks[i] = _webSocketListeners[i].ProcessWebSocketConnectedAsync(connection, context); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + + await connection.ReceiveAsync().ConfigureAwait(false); + _logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress); + } + } + catch (Exception ex) // Otherwise ASP.Net will ignore the exception + { + _logger.LogError(ex, "WS {IP} WebSocketRequestHandler error", context.Connection.RemoteIpAddress); + if (!context.Response.HasStarted) + { + context.Response.StatusCode = 500; + } + } + } + + /// <summary> + /// Processes the web socket message received. + /// </summary> + /// <param name="result">The result.</param> + private async Task ProcessWebSocketMessageReceived(WebSocketMessageInfo result) + { + var tasks = new Task[_webSocketListeners.Length]; + for (var i = 0; i < _webSocketListeners.Length; ++i) + { + tasks[i] = _webSocketListeners[i].ProcessMessageAsync(result); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + } + } +} diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs new file mode 100644 index 00000000..f6340840 --- /dev/null +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -0,0 +1,215 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.IO +{ + public sealed class FileRefresher : IDisposable + { + private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + private readonly IServerConfigurationManager _configurationManager; + + private readonly List<string> _affectedPaths = new(); + private readonly Lock _timerLock = new(); + private Timer? _timer; + private bool _disposed; + + public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger) + { + logger.LogDebug("New file refresher created for {0}", path); + Path = path; + + _configurationManager = configurationManager; + _libraryManager = libraryManager; + _logger = logger; + AddPath(path); + } + + public event EventHandler<EventArgs>? Completed; + + public string Path { get; private set; } + + private void AddAffectedPath(string path) + { + ArgumentException.ThrowIfNullOrEmpty(path); + + if (!_affectedPaths.Contains(path, StringComparer.Ordinal)) + { + _affectedPaths.Add(path); + } + } + + public void AddPath(string path) + { + ArgumentException.ThrowIfNullOrEmpty(path); + + lock (_timerLock) + { + AddAffectedPath(path); + } + + RestartTimer(); + } + + public void RestartTimer() + { + if (_disposed) + { + return; + } + + lock (_timerLock) + { + if (_disposed) + { + return; + } + + if (_timer is null) + { + _timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); + } + else + { + _timer.Change(TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); + } + } + } + + public void ResetPath(string path, string? affectedFile) + { + lock (_timerLock) + { + _logger.LogDebug("Resetting file refresher from {0} to {1}", Path, path); + + Path = path; + AddAffectedPath(path); + + if (!string.IsNullOrEmpty(affectedFile)) + { + AddAffectedPath(affectedFile); + } + } + + RestartTimer(); + } + + private void OnTimerCallback(object? state) + { + List<string> paths; + + lock (_timerLock) + { + paths = _affectedPaths.ToList(); + } + + _logger.LogDebug("Timer stopped."); + + DisposeTimer(); + Completed?.Invoke(this, EventArgs.Empty); + + try + { + ProcessPathChanges(paths); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing directory changes"); + } + } + + private void ProcessPathChanges(List<string> paths) + { + IEnumerable<BaseItem> itemsToRefresh = paths + .Distinct() + .Select(GetAffectedBaseItem) + .Where(item => item is not null) + .DistinctBy(x => x!.Id)!; // Removed null values in the previous .Where() + + foreach (var item in itemsToRefresh) + { + if (item is AggregateFolder) + { + continue; + } + + _logger.LogInformation("{Name} ({Path}) will be refreshed.", item.Name, item.Path); + + try + { + item.ChangedExternally(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error refreshing {Name}", item.Name); + } + } + } + + /// <summary> + /// Gets the affected base item. + /// </summary> + /// <param name="path">The path.</param> + /// <returns>BaseItem.</returns> + private BaseItem? GetAffectedBaseItem(string path) + { + BaseItem? item = null; + + while (item is null && !string.IsNullOrEmpty(path)) + { + item = _libraryManager.FindByPath(path, null); + + path = System.IO.Path.GetDirectoryName(path) ?? string.Empty; + } + + if (item is not null) + { + // If the item has been deleted find the first valid parent that still exists + while (!Directory.Exists(item.Path) && !File.Exists(item.Path)) + { + item = item.GetOwner() ?? item.GetParent(); + + if (item is null) + { + break; + } + } + } + + return item; + } + + private void DisposeTimer() + { + lock (_timerLock) + { + if (_timer is not null) + { + _timer.Dispose(); + _timer = null; + } + } + } + + /// <inheritdoc /> + public void Dispose() + { + if (_disposed) + { + return; + } + + DisposeTimer(); + _disposed = true; + } + } +} diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs new file mode 100644 index 00000000..7cff2a25 --- /dev/null +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -0,0 +1,491 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Emby.Server.Implementations.Library; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.IO; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.IO +{ + /// <inheritdoc cref="ILibraryMonitor" /> + public sealed class LibraryMonitor : ILibraryMonitor, IDisposable + { + private readonly ILogger<LibraryMonitor> _logger; + private readonly ILibraryManager _libraryManager; + private readonly IServerConfigurationManager _configurationManager; + private readonly IFileSystem _fileSystem; + + /// <summary> + /// The file system watchers. + /// </summary> + private readonly ConcurrentDictionary<string, FileSystemWatcher> _fileSystemWatchers = new(StringComparer.OrdinalIgnoreCase); + + /// <summary> + /// The affected paths. + /// </summary> + private readonly List<FileRefresher> _activeRefreshers = []; + + /// <summary> + /// A dynamic list of paths that should be ignored. Added to during our own file system modifications. + /// </summary> + private readonly ConcurrentDictionary<string, string> _tempIgnoredPaths = new(StringComparer.OrdinalIgnoreCase); + + private bool _disposed; + + /// <summary> + /// Initializes a new instance of the <see cref="LibraryMonitor" /> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="libraryManager">The library manager.</param> + /// <param name="configurationManager">The configuration manager.</param> + /// <param name="fileSystem">The filesystem.</param> + /// <param name="appLifetime">The <see cref="IHostApplicationLifetime"/>.</param> + public LibraryMonitor( + ILogger<LibraryMonitor> logger, + ILibraryManager libraryManager, + IServerConfigurationManager configurationManager, + IFileSystem fileSystem, + IHostApplicationLifetime appLifetime) + { + _libraryManager = libraryManager; + _logger = logger; + _configurationManager = configurationManager; + _fileSystem = fileSystem; + + appLifetime.ApplicationStarted.Register(Start); + } + + /// <inheritdoc /> + public void ReportFileSystemChangeBeginning(string path) + { + ArgumentException.ThrowIfNullOrEmpty(path); + + _tempIgnoredPaths[path] = path; + } + + /// <inheritdoc /> + public async void ReportFileSystemChangeComplete(string path, bool refreshPath) + { + ArgumentException.ThrowIfNullOrEmpty(path); + + // This is an arbitrary amount of time, but delay it because file system writes often trigger events long after the file was actually written to. + // Seeing long delays in some situations, especially over the network, sometimes up to 45 seconds + // But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata + await Task.Delay(45000).ConfigureAwait(false); + + _tempIgnoredPaths.TryRemove(path, out _); + + if (refreshPath) + { + try + { + ReportFileSystemChanged(path); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in ReportFileSystemChanged for {Path}", path); + } + } + } + + private bool IsLibraryMonitorEnabled(BaseItem item) + { + if (item is BasePluginFolder) + { + return false; + } + + var options = _libraryManager.GetLibraryOptions(item); + + return options is not null && options.EnableRealtimeMonitor; + } + + /// <inheritdoc /> + public void Start() + { + _libraryManager.ItemAdded += OnLibraryManagerItemAdded; + _libraryManager.ItemRemoved += OnLibraryManagerItemRemoved; + + var pathsToWatch = new List<string>(); + + var paths = _libraryManager + .RootFolder + .Children + .Where(IsLibraryMonitorEnabled) + .OfType<Folder>() + .SelectMany(f => f.PhysicalLocations) + .Distinct() + .Order(); + + foreach (var path in paths) + { + if (!ContainsParentFolder(pathsToWatch, path)) + { + pathsToWatch.Add(path); + } + } + + foreach (var path in pathsToWatch) + { + StartWatchingPath(path); + } + } + + private void StartWatching(BaseItem item) + { + if (IsLibraryMonitorEnabled(item)) + { + StartWatchingPath(item.Path); + } + } + + /// <summary> + /// Handles the ItemRemoved event of the LibraryManager control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param> + private void OnLibraryManagerItemRemoved(object? sender, ItemChangeEventArgs e) + { + if (e.Parent is AggregateFolder) + { + StopWatchingPath(e.Item.Path); + } + } + + /// <summary> + /// Handles the ItemAdded event of the LibraryManager control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param> + private void OnLibraryManagerItemAdded(object? sender, ItemChangeEventArgs e) + { + if (e.Parent is AggregateFolder) + { + StartWatching(e.Item); + } + } + + /// <summary> + /// Examine a list of strings assumed to be file paths to see if it contains a parent of + /// the provided path. + /// </summary> + /// <param name="lst">The LST.</param> + /// <param name="path">The path.</param> + /// <returns><c>true</c> if [contains parent folder] [the specified LST]; otherwise, <c>false</c>.</returns> + /// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c>.</exception> + private static bool ContainsParentFolder(IReadOnlyList<string> lst, ReadOnlySpan<char> path) + { + if (path.IsEmpty) + { + throw new ArgumentException("Path can't be empty", nameof(path)); + } + + path = path.TrimEnd(Path.DirectorySeparatorChar); + + foreach (var str in lst) + { + // this should be a little quicker than examining each actual parent folder... + var compare = str.AsSpan().TrimEnd(Path.DirectorySeparatorChar); + + if (path.Equals(compare, StringComparison.OrdinalIgnoreCase) + || (path.StartsWith(compare, StringComparison.OrdinalIgnoreCase) && path[compare.Length] == Path.DirectorySeparatorChar)) + { + return true; + } + } + + return false; + } + + /// <summary> + /// Starts the watching path. + /// </summary> + /// <param name="path">The path.</param> + private void StartWatchingPath(string path) + { + if (!Directory.Exists(path)) + { + // Seeing a crash in the mono runtime due to an exception being thrown on a different thread + _logger.LogInformation("Skipping realtime monitor for {Path} because the path does not exist", path); + return; + } + + // Already being watched + if (_fileSystemWatchers.ContainsKey(path)) + { + return; + } + + // Creating a FileSystemWatcher over the LAN can take hundreds of milliseconds, so wrap it in a Task to do them all in parallel + Task.Run(() => + { + try + { + var newWatcher = new FileSystemWatcher(path, "*") + { + IncludeSubdirectories = true, + InternalBufferSize = 65536, + NotifyFilter = NotifyFilters.CreationTime | + NotifyFilters.DirectoryName | + NotifyFilters.FileName | + NotifyFilters.LastWrite | + NotifyFilters.Size | + NotifyFilters.Attributes + }; + + newWatcher.Created += OnWatcherChanged; + newWatcher.Deleted += OnWatcherChanged; + newWatcher.Renamed += OnWatcherChanged; + newWatcher.Changed += OnWatcherChanged; + newWatcher.Error += OnWatcherError; + + if (_fileSystemWatchers.TryAdd(path, newWatcher)) + { + newWatcher.EnableRaisingEvents = true; + _logger.LogInformation("Watching directory {Path}", path); + } + else + { + DisposeWatcher(newWatcher, false); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error watching path: {Path}", path); + } + }); + } + + /// <summary> + /// Stops the watching path. + /// </summary> + /// <param name="path">The path.</param> + private void StopWatchingPath(string path) + { + if (_fileSystemWatchers.TryGetValue(path, out var watcher)) + { + DisposeWatcher(watcher, true); + } + } + + /// <summary> + /// Disposes the watcher. + /// </summary> + private void DisposeWatcher(FileSystemWatcher watcher, bool removeFromList) + { + try + { + using (watcher) + { + _logger.LogInformation("Stopping directory watching for path {Path}", watcher.Path); + + watcher.Created -= OnWatcherChanged; + watcher.Deleted -= OnWatcherChanged; + watcher.Renamed -= OnWatcherChanged; + watcher.Changed -= OnWatcherChanged; + watcher.Error -= OnWatcherError; + + watcher.EnableRaisingEvents = false; + } + } + finally + { + if (removeFromList) + { + _fileSystemWatchers.TryRemove(watcher.Path, out _); + } + } + } + + /// <summary> + /// Handles the Error event of the watcher control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="ErrorEventArgs" /> instance containing the event data.</param> + private void OnWatcherError(object sender, ErrorEventArgs e) + { + var ex = e.GetException(); + var dw = (FileSystemWatcher)sender; + + if (ex is UnauthorizedAccessException unauthorizedAccessException) + { + _logger.LogError(unauthorizedAccessException, "Permission error for Directory watcher: {Path}", dw.Path); + return; + } + + _logger.LogError(ex, "Error in Directory watcher for: {Path}", dw.Path); + + DisposeWatcher(dw, true); + } + + /// <summary> + /// Handles the Changed event of the watcher control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="FileSystemEventArgs" /> instance containing the event data.</param> + private void OnWatcherChanged(object sender, FileSystemEventArgs e) + { + try + { + ReportFileSystemChanged(e.FullPath); + } + catch (Exception ex) + { + _logger.LogError(ex, "Exception in ReportFileSystemChanged. Path: {FullPath}", e.FullPath); + } + } + + /// <inheritdoc /> + public void ReportFileSystemChanged(string path) + { + ArgumentException.ThrowIfNullOrEmpty(path); + + if (IgnorePatterns.ShouldIgnore(path)) + { + return; + } + + var fileInfo = _fileSystem.GetFileSystemInfo(path); + if (DotIgnoreIgnoreRule.IsIgnored(fileInfo, null)) + { + return; + } + + // Ignore certain files, If the parent of an ignored path has a change event, ignore that too + foreach (var i in _tempIgnoredPaths.Keys) + { + if (_fileSystem.AreEqual(i, path) + || _fileSystem.ContainsSubPath(i, path)) + { + _logger.LogDebug("Ignoring change to {Path}", path); + return; + } + + // Go up a level + var parent = Path.GetDirectoryName(i); + if (!string.IsNullOrEmpty(parent) && _fileSystem.AreEqual(parent, path)) + { + _logger.LogDebug("Ignoring change to {Path}", path); + return; + } + } + + CreateRefresher(path); + } + + private void CreateRefresher(string path) + { + var parentPath = Path.GetDirectoryName(path); + + lock (_activeRefreshers) + { + foreach (var refresher in _activeRefreshers) + { + // Path is already being refreshed + if (_fileSystem.AreEqual(path, refresher.Path)) + { + refresher.RestartTimer(); + return; + } + + // Parent folder is already being refreshed + if (_fileSystem.ContainsSubPath(refresher.Path, path)) + { + refresher.AddPath(path); + return; + } + + // New path is a parent + if (_fileSystem.ContainsSubPath(path, refresher.Path)) + { + refresher.ResetPath(path, null); + return; + } + + // They are siblings. Rebase the refresher to the parent folder. + if (parentPath is not null + && Path.GetDirectoryName(refresher.Path.AsSpan()).Equals(parentPath, StringComparison.Ordinal)) + { + refresher.ResetPath(parentPath, path); + return; + } + } + + var newRefresher = new FileRefresher(path, _configurationManager, _libraryManager, _logger); + newRefresher.Completed += OnNewRefresherCompleted; + _activeRefreshers.Add(newRefresher); + } + } + + private void OnNewRefresherCompleted(object? sender, EventArgs e) + { + if (sender is null) + { + return; + } + + var refresher = (FileRefresher)sender; + DisposeRefresher(refresher); + } + + /// <summary> + /// Stops this instance. + /// </summary> + public void Stop() + { + _libraryManager.ItemAdded -= OnLibraryManagerItemAdded; + _libraryManager.ItemRemoved -= OnLibraryManagerItemRemoved; + + foreach (var watcher in _fileSystemWatchers.Values.ToList()) + { + DisposeWatcher(watcher, false); + } + + _fileSystemWatchers.Clear(); + DisposeRefreshers(); + } + + private void DisposeRefresher(FileRefresher refresher) + { + lock (_activeRefreshers) + { + refresher.Completed -= OnNewRefresherCompleted; + refresher.Dispose(); + _activeRefreshers.Remove(refresher); + } + } + + private void DisposeRefreshers() + { + lock (_activeRefreshers) + { + foreach (var refresher in _activeRefreshers) + { + refresher.Completed -= OnNewRefresherCompleted; + refresher.Dispose(); + } + + _activeRefreshers.Clear(); + } + } + + /// <inheritdoc /> + public void Dispose() + { + if (_disposed) + { + return; + } + + Stop(); + _disposed = true; + } + } +} diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs new file mode 100644 index 00000000..4d68cb44 --- /dev/null +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -0,0 +1,716 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Security; +using Jellyfin.Extensions; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.IO; +using MediaBrowser.Model.IO; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.IO +{ + /// <summary> + /// Class ManagedFileSystem. + /// </summary> + public class ManagedFileSystem : IFileSystem + { + private static readonly bool _isEnvironmentCaseInsensitive = OperatingSystem.IsWindows(); + private static readonly char[] _invalidPathCharacters = + { + '\"', '<', '>', '|', '\0', + (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, + (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, + (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, + (char)31, ':', '*', '?', '\\', '/' + }; + + private readonly ILogger<ManagedFileSystem> _logger; + private readonly List<IShortcutHandler> _shortcutHandlers; + private readonly string _tempPath; + + /// <summary> + /// Initializes a new instance of the <see cref="ManagedFileSystem"/> class. + /// </summary> + /// <param name="logger">The <see cref="ILogger"/> instance to use.</param> + /// <param name="applicationPaths">The <see cref="IApplicationPaths"/> instance to use.</param> + /// <param name="shortcutHandlers">the <see cref="IShortcutHandler"/>'s to use.</param> + public ManagedFileSystem( + ILogger<ManagedFileSystem> logger, + IApplicationPaths applicationPaths, + IEnumerable<IShortcutHandler> shortcutHandlers) + { + _logger = logger; + _tempPath = applicationPaths.TempDirectory; + _shortcutHandlers = shortcutHandlers.ToList(); + } + + /// <summary> + /// Determines whether the specified filename is shortcut. + /// </summary> + /// <param name="filename">The filename.</param> + /// <returns><c>true</c> if the specified filename is shortcut; otherwise, <c>false</c>.</returns> + /// <exception cref="ArgumentNullException"><paramref name="filename"/> is <c>null</c>.</exception> + public virtual bool IsShortcut(string filename) + { + ArgumentException.ThrowIfNullOrEmpty(filename); + + var extension = Path.GetExtension(filename); + return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase)); + } + + /// <summary> + /// Resolves the shortcut. + /// </summary> + /// <param name="filename">The filename.</param> + /// <returns>System.String.</returns> + /// <exception cref="ArgumentNullException"><paramref name="filename"/> is <c>null</c>.</exception> + public virtual string? ResolveShortcut(string filename) + { + ArgumentException.ThrowIfNullOrEmpty(filename); + + var extension = Path.GetExtension(filename); + var handler = _shortcutHandlers.Find(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase)); + + return handler?.Resolve(filename); + } + + /// <inheritdoc /> + public virtual string MakeAbsolutePath(string folderPath, string filePath) + { + // path is actually a stream + if (string.IsNullOrWhiteSpace(filePath)) + { + return filePath; + } + + var isAbsolutePath = Path.IsPathRooted(filePath) && (!OperatingSystem.IsWindows() || filePath[0] != '\\'); + + if (isAbsolutePath) + { + // absolute local path + return filePath; + } + + // unc path + if (filePath.StartsWith(@"\\", StringComparison.Ordinal)) + { + return filePath; + } + + var filePathSpan = filePath.AsSpan(); + + // relative path on windows + if (filePath[0] == '\\') + { + filePathSpan = filePathSpan.Slice(1); + } + + try + { + return Path.GetFullPath(Path.Join(folderPath, filePathSpan)); + } + catch (ArgumentException) + { + return filePath; + } + catch (PathTooLongException) + { + return filePath; + } + catch (NotSupportedException) + { + return filePath; + } + } + + /// <summary> + /// Creates the shortcut. + /// </summary> + /// <param name="shortcutPath">The shortcut path.</param> + /// <param name="target">The target.</param> + /// <exception cref="ArgumentNullException">The shortcutPath or target is null.</exception> + public virtual void CreateShortcut(string shortcutPath, string target) + { + ArgumentException.ThrowIfNullOrEmpty(shortcutPath); + ArgumentException.ThrowIfNullOrEmpty(target); + + var extension = Path.GetExtension(shortcutPath); + var handler = _shortcutHandlers.Find(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase)); + + if (handler is not null) + { + handler.Create(shortcutPath, target); + } + else + { + throw new NotImplementedException(); + } + } + + /// <inheritdoc /> + public void MoveDirectory(string source, string destination) + { + // Make sure parent directory of target exists + var parent = Directory.GetParent(destination); + parent?.Create(); + + try + { + Directory.Move(source, destination); + } + catch (IOException) + { + // Cross device move requires a copy + Directory.CreateDirectory(destination); + var sourceDir = new DirectoryInfo(source); + foreach (var file in sourceDir.EnumerateFiles()) + { + file.CopyTo(Path.Combine(destination, file.Name), true); + } + + sourceDir.Delete(true); + } + } + + /// <summary> + /// Returns a <see cref="FileSystemMetadata"/> object for the specified file or directory path. + /// </summary> + /// <param name="path">A path to a file or directory.</param> + /// <returns>A <see cref="FileSystemMetadata"/> object.</returns> + /// <remarks>If the specified path points to a directory, the returned <see cref="FileSystemMetadata"/> object's + /// <see cref="FileSystemMetadata.IsDirectory"/> property will be set to true and all other properties will reflect the properties of the directory.</remarks> + public virtual FileSystemMetadata GetFileSystemInfo(string path) + { + // Take a guess to try and avoid two file system hits, but we'll double-check by calling Exists + if (Path.HasExtension(path)) + { + var fileInfo = new FileInfo(path); + + if (fileInfo.Exists) + { + return GetFileSystemMetadata(fileInfo); + } + + return GetFileSystemMetadata(new DirectoryInfo(path)); + } + else + { + var fileInfo = new DirectoryInfo(path); + + if (fileInfo.Exists) + { + return GetFileSystemMetadata(fileInfo); + } + + return GetFileSystemMetadata(new FileInfo(path)); + } + } + + /// <summary> + /// Returns a <see cref="FileSystemMetadata"/> object for the specified file path. + /// </summary> + /// <param name="path">A path to a file.</param> + /// <returns>A <see cref="FileSystemMetadata"/> object.</returns> + /// <remarks><para>If the specified path points to a directory, the returned <see cref="FileSystemMetadata"/> object's + /// <see cref="FileSystemMetadata.IsDirectory"/> property and the <see cref="FileSystemMetadata.Exists"/> property will both be set to false.</para> + /// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo"/>.</para></remarks> + public virtual FileSystemMetadata GetFileInfo(string path) + { + var fileInfo = new FileInfo(path); + + return GetFileSystemMetadata(fileInfo); + } + + /// <summary> + /// Returns a <see cref="FileSystemMetadata"/> object for the specified directory path. + /// </summary> + /// <param name="path">A path to a directory.</param> + /// <returns>A <see cref="FileSystemMetadata"/> object.</returns> + /// <remarks><para>If the specified path points to a file, the returned <see cref="FileSystemMetadata"/> object's + /// <see cref="FileSystemMetadata.IsDirectory"/> property will be set to true and the <see cref="FileSystemMetadata.Exists"/> property will be set to false.</para> + /// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo"/>.</para></remarks> + public virtual FileSystemMetadata GetDirectoryInfo(string path) + { + var fileInfo = new DirectoryInfo(path); + + return GetFileSystemMetadata(fileInfo); + } + + private FileSystemMetadata GetFileSystemMetadata(FileSystemInfo info) + { + var result = new FileSystemMetadata + { + Exists = info.Exists, + FullName = info.FullName, + Extension = info.Extension, + Name = info.Name + }; + + if (result.Exists) + { + result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttributes.Directory; + + if (info is FileInfo fileInfo) + { + result.CreationTimeUtc = GetCreationTimeUtc(info); + result.LastWriteTimeUtc = GetLastWriteTimeUtc(info); + if (fileInfo.LinkTarget is not null) + { + try + { + var targetFileInfo = FileSystemHelper.ResolveLinkTarget(fileInfo, returnFinalTarget: true); + if (targetFileInfo is not null) + { + result.Exists = targetFileInfo.Exists; + if (result.Exists) + { + result.Length = targetFileInfo.Length; + result.CreationTimeUtc = GetCreationTimeUtc(targetFileInfo); + result.LastWriteTimeUtc = GetLastWriteTimeUtc(targetFileInfo); + } + } + else + { + result.Exists = false; + } + } + catch (UnauthorizedAccessException ex) + { + _logger.LogError(ex, "Reading the file at {Path} failed due to a permissions exception.", fileInfo.FullName); + } + } + else + { + result.Length = fileInfo.Length; + } + } + } + else + { + result.IsDirectory = info is DirectoryInfo; + } + + return result; + } + + /// <summary> + /// Takes a filename and removes invalid characters. + /// </summary> + /// <param name="filename">The filename.</param> + /// <returns>System.String.</returns> + /// <exception cref="ArgumentNullException">The filename is null.</exception> + public string GetValidFilename(string filename) + { + var first = filename.IndexOfAny(_invalidPathCharacters); + if (first == -1) + { + // Fast path for clean strings + return filename; + } + + return string.Create( + filename.Length, + (filename, _invalidPathCharacters, first), + (chars, state) => + { + state.filename.AsSpan().CopyTo(chars); + + chars[state.first++] = ' '; + + var len = chars.Length; + foreach (var c in state._invalidPathCharacters) + { + for (int i = state.first; i < len; i++) + { + if (chars[i] == c) + { + chars[i] = ' '; + } + } + } + }); + } + + /// <summary> + /// Gets the creation time UTC. + /// </summary> + /// <param name="info">The info.</param> + /// <returns>DateTime.</returns> + public DateTime GetCreationTimeUtc(FileSystemInfo info) + { + // This could throw an error on some file systems that have dates out of range + try + { + return info.CreationTimeUtc; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error determining CreationTimeUtc for {FullName}", info.FullName); + return DateTime.MinValue; + } + } + + /// <inheritdoc /> + public virtual DateTime GetCreationTimeUtc(string path) + { + return GetCreationTimeUtc(GetFileSystemInfo(path)); + } + + /// <inheritdoc /> + public virtual DateTime GetCreationTimeUtc(FileSystemMetadata info) + { + return info.CreationTimeUtc; + } + + /// <inheritdoc /> + public virtual DateTime GetLastWriteTimeUtc(FileSystemMetadata info) + { + return info.LastWriteTimeUtc; + } + + /// <summary> + /// Gets the creation time UTC. + /// </summary> + /// <param name="info">The info.</param> + /// <returns>DateTime.</returns> + public DateTime GetLastWriteTimeUtc(FileSystemInfo info) + { + // This could throw an error on some file systems that have dates out of range + try + { + return info.LastWriteTimeUtc; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error determining LastAccessTimeUtc for {FullName}", info.FullName); + return DateTime.MinValue; + } + } + + /// <inheritdoc /> + public virtual DateTime GetLastWriteTimeUtc(string path) + { + return GetLastWriteTimeUtc(GetFileSystemInfo(path)); + } + + /// <inheritdoc /> + public virtual void SetHidden(string path, bool isHidden) + { + if (!OperatingSystem.IsWindows()) + { + return; + } + + var info = new FileInfo(path); + + if (info.Exists && + (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden != isHidden) + { + if (isHidden) + { + File.SetAttributes(path, info.Attributes | FileAttributes.Hidden); + } + else + { + File.SetAttributes(path, info.Attributes & ~FileAttributes.Hidden); + } + } + } + + /// <inheritdoc /> + public virtual void SetAttributes(string path, bool isHidden, bool readOnly) + { + if (!OperatingSystem.IsWindows()) + { + return; + } + + var info = new FileInfo(path); + + if (!info.Exists) + { + return; + } + + if ((info.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly == readOnly + && (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden == isHidden) + { + return; + } + + var attributes = info.Attributes; + + if (readOnly) + { + attributes |= FileAttributes.ReadOnly; + } + else + { + attributes &= ~FileAttributes.ReadOnly; + } + + if (isHidden) + { + attributes |= FileAttributes.Hidden; + } + else + { + attributes &= ~FileAttributes.Hidden; + } + + File.SetAttributes(path, attributes); + } + + /// <inheritdoc /> + public virtual void SwapFiles(string file1, string file2) + { + ArgumentException.ThrowIfNullOrEmpty(file1); + ArgumentException.ThrowIfNullOrEmpty(file2); + + var temp1 = Path.Combine(_tempPath, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)); + + // Copying over will fail against hidden files + SetHidden(file1, false); + SetHidden(file2, false); + + Directory.CreateDirectory(_tempPath); + File.Copy(file1, temp1, true); + + File.Copy(file2, file1, true); + File.Move(temp1, file2, true); + } + + /// <inheritdoc /> + public virtual bool ContainsSubPath(string parentPath, string path) + { + ArgumentException.ThrowIfNullOrEmpty(parentPath); + ArgumentException.ThrowIfNullOrEmpty(path); + + return path.Contains( + Path.TrimEndingDirectorySeparator(parentPath) + Path.DirectorySeparatorChar, + _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); + } + + /// <inheritdoc /> + public virtual bool AreEqual(string path1, string path2) + { + if (string.IsNullOrWhiteSpace(path1) || string.IsNullOrWhiteSpace(path2)) + { + return false; + } + + var normalized1 = Path.TrimEndingDirectorySeparator(path1); + var normalized2 = Path.TrimEndingDirectorySeparator(path2); + + return string.Equals( + normalized1, + normalized2, + _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); + } + + /// <inheritdoc /> + public virtual string GetFileNameWithoutExtension(FileSystemMetadata info) + { + if (info.IsDirectory) + { + return info.Name; + } + + return Path.GetFileNameWithoutExtension(info.FullName); + } + + /// <inheritdoc /> + public virtual bool IsPathFile(string path) + { + if (path.Contains("://", StringComparison.OrdinalIgnoreCase) + && !path.StartsWith("file://", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + return true; + } + + /// <inheritdoc /> + public virtual void DeleteFile(string path) + { + SetAttributes(path, false, false); + File.Delete(path); + } + + /// <inheritdoc /> + public virtual IEnumerable<FileSystemMetadata> GetDrives() + { + // check for ready state to avoid waiting for drives to timeout + // some drives on linux have no actual size or are used for other purposes + return DriveInfo.GetDrives() + .Where( + d => (d.DriveType == DriveType.Fixed || d.DriveType == DriveType.Network || d.DriveType == DriveType.Removable) + && d.IsReady + && d.TotalSize != 0) + .Select(d => new FileSystemMetadata + { + Name = d.Name, + FullName = d.RootDirectory.FullName, + IsDirectory = true + }); + } + + /// <inheritdoc /> + public virtual IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false) + { + return ToMetadata(new DirectoryInfo(path).EnumerateDirectories("*", GetEnumerationOptions(recursive))); + } + + /// <inheritdoc /> + public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false) + { + return GetFiles(path, "*", recursive); + } + + /// <inheritdoc /> + public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, string searchPattern, bool recursive = false) + { + return GetFiles(path, searchPattern, null, false, recursive); + } + + /// <inheritdoc /> + public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string>? extensions, bool enableCaseSensitiveExtensions, bool recursive) + { + return GetFiles(path, "*", extensions, enableCaseSensitiveExtensions, recursive); + } + + /// <inheritdoc /> + public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, string searchPattern, IReadOnlyList<string>? extensions, bool enableCaseSensitiveExtensions, bool recursive = false) + { + var enumerationOptions = GetEnumerationOptions(recursive); + + // On linux and macOS the search pattern is case-sensitive + // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method + if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions is not null && extensions.Count == 1) + { + searchPattern = searchPattern.EndsWith(extensions[0], StringComparison.Ordinal) ? searchPattern : searchPattern + extensions[0]; + + return ToMetadata(new DirectoryInfo(path).EnumerateFiles(searchPattern, enumerationOptions)); + } + + var files = new DirectoryInfo(path).EnumerateFiles(searchPattern, enumerationOptions); + + if (extensions is not null && extensions.Count > 0) + { + files = files.Where(i => + { + var ext = i.Extension.AsSpan(); + if (ext.IsEmpty) + { + return false; + } + + return extensions.Contains(ext, StringComparison.OrdinalIgnoreCase); + }); + } + + return ToMetadata(files); + } + + /// <inheritdoc /> + public virtual IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false) + { + // Note: any of unhandled exceptions thrown by this method may cause the caller to believe the whole path is not accessible. + // But what causing the exception may be a single file under that path. This could lead to unexpected behavior. + // For example, the scanner will remove everything in that path due to unhandled errors. + var directoryInfo = new DirectoryInfo(path); + var enumerationOptions = GetEnumerationOptions(recursive); + + return ToMetadata(directoryInfo.EnumerateFileSystemInfos("*", enumerationOptions)); + } + + private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos) + { + return infos.Select(GetFileSystemMetadata); + } + + /// <inheritdoc /> + public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false) + { + return Directory.EnumerateDirectories(path, "*", GetEnumerationOptions(recursive)); + } + + /// <inheritdoc /> + public virtual IEnumerable<string> GetFilePaths(string path, bool recursive = false) + { + return GetFilePaths(path, null, false, recursive); + } + + /// <inheritdoc /> + public virtual IEnumerable<string> GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExtensions, bool recursive = false) + { + var enumerationOptions = GetEnumerationOptions(recursive); + + // On linux and macOS the search pattern is case-sensitive + // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method + if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions is not null && extensions.Length == 1) + { + return Directory.EnumerateFiles(path, "*" + extensions[0], enumerationOptions); + } + + var files = Directory.EnumerateFiles(path, "*", enumerationOptions); + + if (extensions is not null && extensions.Length > 0) + { + files = files.Where(i => + { + var ext = Path.GetExtension(i.AsSpan()); + if (ext.IsEmpty) + { + return false; + } + + return extensions.Contains(ext, StringComparison.OrdinalIgnoreCase); + }); + } + + return files; + } + + /// <inheritdoc /> + public virtual IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false) + { + try + { + return Directory.EnumerateFileSystemEntries(path, "*", GetEnumerationOptions(recursive)); + } + catch (Exception ex) when (ex is UnauthorizedAccessException or DirectoryNotFoundException or SecurityException) + { + _logger.LogError(ex, "Failed to enumerate path {Path}", path); + return Enumerable.Empty<string>(); + } + } + + /// <inheritdoc /> + public virtual bool DirectoryExists(string path) + { + return Directory.Exists(path); + } + + /// <inheritdoc /> + public virtual bool FileExists(string path) + { + return File.Exists(path); + } + + private EnumerationOptions GetEnumerationOptions(bool recursive) + { + return new EnumerationOptions + { + RecurseSubdirectories = recursive, + IgnoreInaccessible = true, + // Don't skip any files. + AttributesToSkip = 0 + }; + } + } +} diff --git a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs new file mode 100644 index 00000000..5776c7a7 --- /dev/null +++ b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs @@ -0,0 +1,35 @@ +#pragma warning disable CS1591 + +using System; +using System.IO; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.IO +{ + public class MbLinkShortcutHandler : IShortcutHandler + { + public string Extension => ".mblink"; + + public string? Resolve(string shortcutPath) + { + ArgumentException.ThrowIfNullOrEmpty(shortcutPath); + + if (Path.GetExtension(shortcutPath.AsSpan()).Equals(".mblink", StringComparison.OrdinalIgnoreCase)) + { + var path = File.ReadAllText(shortcutPath); + + return Path.TrimEndingDirectorySeparator(path); + } + + return null; + } + + public void Create(string shortcutPath, string targetPath) + { + ArgumentException.ThrowIfNullOrEmpty(shortcutPath); + ArgumentException.ThrowIfNullOrEmpty(targetPath); + + File.WriteAllText(shortcutPath, targetPath); + } + } +} diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs new file mode 100644 index 00000000..b7bcaace --- /dev/null +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -0,0 +1,28 @@ +namespace Emby.Server.Implementations +{ + /// <summary> + /// Specifies the contract for server startup options. + /// </summary> + public interface IStartupOptions + { + /// <summary> + /// Gets the value of the --ffmpeg command line option. + /// </summary> + string? FFmpegPath { get; } + + /// <summary> + /// Gets a value indicating whether to run as service by the --service command line option. + /// </summary> + bool IsService { get; } + + /// <summary> + /// Gets the value of the --package-name command line option. + /// </summary> + string? PackageName { get; } + + /// <summary> + /// Gets the value of the --published-server-url command line option. + /// </summary> + string? PublishedServerUrl { get; } + } +} diff --git a/Emby.Server.Implementations/Images/ArtistImageProvider.cs b/Emby.Server.Implementations/Images/ArtistImageProvider.cs new file mode 100644 index 00000000..e96b6459 --- /dev/null +++ b/Emby.Server.Implementations/Images/ArtistImageProvider.cs @@ -0,0 +1,46 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.Images +{ + /// <summary> + /// Class ArtistImageProvider. + /// </summary> + public class ArtistImageProvider : BaseDynamicImageProvider<MusicArtist> + { + public ArtistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) + : base(fileSystem, providerManager, applicationPaths, imageProcessor) + { + } + + /// <summary> + /// Get children objects used to create an artist image. + /// </summary> + /// <param name="item">The artist used to create the image.</param> + /// <returns>Any relevant children objects.</returns> + protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item) + { + return Array.Empty<BaseItem>(); + + // TODO enable this when BaseDynamicImageProvider objects are configurable + // return _libraryManager.GetItemList(new InternalItemsQuery + // { + // ArtistIds = new[] { item.Id }, + // IncludeItemTypes = new[] { nameof(MusicAlbum) }, + // OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) }, + // Limit = 4, + // Recursive = true, + // ImageTypes = new[] { ImageType.Primary }, + // DtoOptions = new DtoOptions(false) + // }); + } + } +} diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs new file mode 100644 index 00000000..996cd1b3 --- /dev/null +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -0,0 +1,325 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net.Mime; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; + +namespace Emby.Server.Implementations.Images +{ + public abstract class BaseDynamicImageProvider<T> : IHasItemChangeMonitor, IForcedProvider, ICustomMetadataProvider<T>, IHasOrder + where T : BaseItem + { + protected BaseDynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) + { + ApplicationPaths = applicationPaths; + ProviderManager = providerManager; + FileSystem = fileSystem; + ImageProcessor = imageProcessor; + } + + protected IFileSystem FileSystem { get; } + + protected IProviderManager ProviderManager { get; } + + protected IApplicationPaths ApplicationPaths { get; } + + protected IImageProcessor ImageProcessor { get; set; } + + protected virtual IReadOnlyCollection<ImageType> SupportedImages { get; } + = [ImageType.Primary]; + + /// <inheritdoc /> + public string Name => "Dynamic Image Provider"; + + public int Order => 0; + + protected virtual bool Supports(BaseItem item) => true; + + public async Task<ItemUpdateType> FetchAsync(T item, MetadataRefreshOptions options, CancellationToken cancellationToken) + { + if (!Supports(item)) + { + return ItemUpdateType.None; + } + + var updateType = ItemUpdateType.None; + + if (SupportedImages.Contains(ImageType.Primary)) + { + var primaryResult = await FetchAsync(item, ImageType.Primary, options, cancellationToken).ConfigureAwait(false); + updateType |= primaryResult; + } + + if (SupportedImages.Contains(ImageType.Thumb)) + { + var thumbResult = await FetchAsync(item, ImageType.Thumb, options, cancellationToken).ConfigureAwait(false); + updateType |= thumbResult; + } + + return updateType; + } + + protected Task<ItemUpdateType> FetchAsync(BaseItem item, ImageType imageType, MetadataRefreshOptions options, CancellationToken cancellationToken) + { + var image = item.GetImageInfo(imageType, 0); + + if (image is not null) + { + if (!image.IsLocalFile) + { + return Task.FromResult(ItemUpdateType.None); + } + + if (!FileSystem.ContainsSubPath(item.GetInternalMetadataPath(), image.Path)) + { + return Task.FromResult(ItemUpdateType.None); + } + } + + var items = GetItemsWithImages(item); + + return FetchToFileInternal(item, items, imageType, cancellationToken); + } + + protected async Task<ItemUpdateType> FetchToFileInternal( + BaseItem item, + IReadOnlyList<BaseItem> itemsWithImages, + ImageType imageType, + CancellationToken cancellationToken) + { + var outputPathWithoutExtension = Path.Combine(ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)); + Directory.CreateDirectory(Path.GetDirectoryName(outputPathWithoutExtension)); + string outputPath = CreateImage(item, itemsWithImages, outputPathWithoutExtension, imageType, 0); + + if (string.IsNullOrEmpty(outputPath)) + { + return ItemUpdateType.None; + } + + var mimeType = MimeTypes.GetMimeType(outputPath); + + if (string.Equals(mimeType, MediaTypeNames.Application.Octet, StringComparison.OrdinalIgnoreCase)) + { + mimeType = MediaTypeNames.Image.Png; + } + + await ProviderManager.SaveImage(item, outputPath, mimeType, imageType, null, false, cancellationToken).ConfigureAwait(false); + + return ItemUpdateType.ImageUpdate; + } + + protected abstract IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item); + + protected string CreateThumbCollage(BaseItem primaryItem, IEnumerable<BaseItem> items, string outputPath) + { + return CreateCollage(primaryItem, items, outputPath, 640, 360); + } + + protected virtual IEnumerable<string> GetStripCollageImagePaths(BaseItem primaryItem, IEnumerable<BaseItem> items) + { + var useBackdrop = primaryItem is CollectionFolder || primaryItem is UserView; + return items + .Select(i => + { + // Use Backdrop instead of Primary image for Library images. + if (useBackdrop) + { + var backdrop = i.GetImageInfo(ImageType.Backdrop, 0); + if (backdrop is not null && backdrop.IsLocalFile) + { + return backdrop.Path; + } + } + + var image = i.GetImageInfo(ImageType.Primary, 0); + if (image is not null && image.IsLocalFile) + { + return image.Path; + } + + image = i.GetImageInfo(ImageType.Thumb, 0); + if (image is not null && image.IsLocalFile) + { + return image.Path; + } + + return null; + }) + .Where(i => !string.IsNullOrEmpty(i)); + } + + protected string CreatePosterCollage(BaseItem primaryItem, IEnumerable<BaseItem> items, string outputPath) + { + return CreateCollage(primaryItem, items, outputPath, 400, 600); + } + + protected string CreateSquareCollage(BaseItem primaryItem, IEnumerable<BaseItem> items, string outputPath) + { + return CreateCollage(primaryItem, items, outputPath, 600, 600); + } + + protected string CreateThumbCollage(BaseItem primaryItem, IEnumerable<BaseItem> items, string outputPath, int width, int height) + { + return CreateCollage(primaryItem, items, outputPath, width, height); + } + + private string CreateCollage(BaseItem primaryItem, IEnumerable<BaseItem> items, string outputPath, int width, int height) + { + Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); + + var options = new ImageCollageOptions + { + Height = height, + Width = width, + OutputPath = outputPath, + InputPaths = GetStripCollageImagePaths(primaryItem, items).ToArray() + }; + + if (options.InputPaths.Count == 0) + { + return null; + } + + if (!ImageProcessor.SupportsImageCollageCreation) + { + return null; + } + + ImageProcessor.CreateImageCollage(options, primaryItem.Name); + return outputPath; + } + + protected virtual string CreateImage( + BaseItem item, + IReadOnlyCollection<BaseItem> itemsWithImages, + string outputPathWithoutExtension, + ImageType imageType, + int imageIndex) + { + if (itemsWithImages.Count == 0) + { + return null; + } + + string outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png"); + + if (imageType == ImageType.Thumb) + { + return CreateThumbCollage(item, itemsWithImages, outputPath); + } + + if (imageType == ImageType.Primary) + { + if (item is UserView + || item is Playlist + || item is MusicGenre + || item is Genre + || item is PhotoAlbum + || item is MusicArtist) + { + return CreateSquareCollage(item, itemsWithImages, outputPath); + } + + return CreatePosterCollage(item, itemsWithImages, outputPath); + } + + throw new ArgumentException("Unexpected image type", nameof(imageType)); + } + + public bool HasChanged(BaseItem item, IDirectoryService directoryService) + { + if (!Supports(item)) + { + return false; + } + + if (SupportedImages.Contains(ImageType.Primary) && HasChanged(item, ImageType.Primary)) + { + return true; + } + + if (SupportedImages.Contains(ImageType.Thumb) && HasChanged(item, ImageType.Thumb)) + { + return true; + } + + return false; + } + + protected bool HasChanged(BaseItem item, ImageType type) + { + var image = item.GetImageInfo(type, 0); + + if (image is null) + { + return GetItemsWithImages(item).Count is not 0; + } + + if (!image.IsLocalFile) + { + return false; + } + + if (!FileSystem.ContainsSubPath(item.GetInternalMetadataPath(), image.Path)) + { + return false; + } + + if (!HasChangedByDate(item, image)) + { + return false; + } + + return true; + } + + protected virtual bool HasChangedByDate(BaseItem item, ItemImageInfo image) + { + var path = image.Path; + if (!string.IsNullOrEmpty(path)) + { + var modificationDate = FileSystem.GetLastWriteTimeUtc(path); + return image.DateModified != modificationDate; + } + + return false; + } + + protected string CreateSingleImage(IEnumerable<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType) + { + var image = itemsWithImages + .Where(i => i.HasImage(imageType) && i.GetImageInfo(imageType, 0).IsLocalFile && Path.HasExtension(i.GetImagePath(imageType))) + .Select(i => i.GetImagePath(imageType)) + .FirstOrDefault(); + + if (string.IsNullOrEmpty(image)) + { + return null; + } + + var ext = Path.GetExtension(image); + + var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ext); + File.Copy(image, outputPath, true); + + return outputPath; + } + } +} diff --git a/Emby.Server.Implementations/Images/BaseFolderImageProvider.cs b/Emby.Server.Implementations/Images/BaseFolderImageProvider.cs new file mode 100644 index 00000000..0d63b3af --- /dev/null +++ b/Emby.Server.Implementations/Images/BaseFolderImageProvider.cs @@ -0,0 +1,66 @@ +#pragma warning disable CS1591 + +using System.Collections.Generic; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.Images +{ + public abstract class BaseFolderImageProvider<T> : BaseDynamicImageProvider<T> + where T : Folder, new() + { + private readonly ILibraryManager _libraryManager; + + protected BaseFolderImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) + : base(fileSystem, providerManager, applicationPaths, imageProcessor) + { + _libraryManager = libraryManager; + } + + protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item) + { + return _libraryManager.GetItemList(new InternalItemsQuery + { + Parent = item, + Recursive = true, + DtoOptions = new DtoOptions(true), + ImageTypes = [ImageType.Primary], + OrderBy = + [ + (ItemSortBy.IsFolder, SortOrder.Ascending), + (ItemSortBy.SortName, SortOrder.Ascending) + ], + Limit = 1 + }); + } + + protected override string CreateImage(BaseItem item, IReadOnlyCollection<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + { + return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary); + } + + protected override bool Supports(BaseItem item) + { + return item is T; + } + + protected override bool HasChangedByDate(BaseItem item, ItemImageInfo image) + { + if (item is MusicAlbum) + { + return false; + } + + return base.HasChangedByDate(item, image); + } + } +} diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs new file mode 100644 index 00000000..a2537332 --- /dev/null +++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs @@ -0,0 +1,108 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.IO; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Images +{ + public class CollectionFolderImageProvider : BaseDynamicImageProvider<CollectionFolder> + { + public CollectionFolderImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) : base(fileSystem, providerManager, applicationPaths, imageProcessor) + { + } + + protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item) + { + var view = (CollectionFolder)item; + var viewType = view.CollectionType; + + BaseItemKind[] includeItemTypes; + + switch (viewType) + { + case CollectionType.movies: + includeItemTypes = new[] { BaseItemKind.Movie }; + break; + case CollectionType.tvshows: + includeItemTypes = new[] { BaseItemKind.Series }; + break; + case CollectionType.music: + includeItemTypes = new[] { BaseItemKind.MusicAlbum }; + break; + case CollectionType.musicvideos: + includeItemTypes = new[] { BaseItemKind.MusicVideo }; + break; + case CollectionType.books: + includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook }; + break; + case CollectionType.boxsets: + includeItemTypes = new[] { BaseItemKind.BoxSet }; + break; + case CollectionType.homevideos: + case CollectionType.photos: + includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Photo }; + break; + default: + includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Audio, BaseItemKind.Photo, BaseItemKind.Movie, BaseItemKind.Series }; + break; + } + + var recursive = viewType != CollectionType.playlists; + + return view.GetItemList(new InternalItemsQuery + { + CollapseBoxSetItems = false, + Recursive = recursive, + DtoOptions = new DtoOptions(false), + ImageTypes = new[] { ImageType.Primary }, + Limit = 8, + OrderBy = new[] + { + (ItemSortBy.Random, SortOrder.Ascending) + }, + IncludeItemTypes = includeItemTypes + }); + } + + protected override bool Supports(BaseItem item) + { + return item is CollectionFolder; + } + + protected override string CreateImage(BaseItem item, IReadOnlyCollection<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + { + var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png"); + + if (imageType == ImageType.Primary) + { + if (itemsWithImages.Count == 0) + { + return null; + } + + return CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540); + } + + return base.CreateImage(item, itemsWithImages, outputPath, imageType, imageIndex); + } + + protected override bool HasChangedByDate(BaseItem item, ItemImageInfo image) + { + var age = DateTime.UtcNow - image.DateModified; + return age.TotalDays > 7; + } + } +} diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs new file mode 100644 index 00000000..6b2ae23b --- /dev/null +++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs @@ -0,0 +1,137 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.Images +{ + public class DynamicImageProvider : BaseDynamicImageProvider<UserView> + { + private readonly IUserManager _userManager; + + public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager) + : base(fileSystem, providerManager, applicationPaths, imageProcessor) + { + _userManager = userManager; + } + + protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item) + { + var view = (UserView)item; + + var isUsingCollectionStrip = IsUsingCollectionStrip(view); + var recursive = isUsingCollectionStrip && view?.ViewType is not null && view.ViewType != CollectionType.boxsets && view.ViewType != CollectionType.playlists; + + var result = view.GetItemList(new InternalItemsQuery + { + User = view.UserId.HasValue ? _userManager.GetUserById(view.UserId.Value) : null, + CollapseBoxSetItems = false, + Recursive = recursive, + ExcludeItemTypes = new[] { BaseItemKind.UserView, BaseItemKind.CollectionFolder, BaseItemKind.Person }, + DtoOptions = new DtoOptions(false) + }); + + var items = result.Select(i => + { + if (i is Episode episode) + { + var series = episode.Series; + if (series is not null) + { + return series; + } + + return episode; + } + + if (i is Season season) + { + var series = season.Series; + if (series is not null) + { + return series; + } + + return season; + } + + if (i is Audio audio) + { + var album = audio.AlbumEntity; + if (album is not null && album.HasImage(ImageType.Primary)) + { + return album; + } + } + + return i; + }).DistinctBy(x => x.Id); + + List<BaseItem> returnItems; + if (isUsingCollectionStrip) + { + returnItems = items + .Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)) + .ToList(); + returnItems.Shuffle(); + return returnItems; + } + + returnItems = items + .Where(i => i.HasImage(ImageType.Primary)) + .ToList(); + returnItems.Shuffle(); + return returnItems; + } + + protected override bool Supports(BaseItem item) + { + if (item is UserView view) + { + return IsUsingCollectionStrip(view); + } + + return false; + } + + private static bool IsUsingCollectionStrip(UserView view) + { + CollectionType[] collectionStripViewTypes = + { + CollectionType.movies, + CollectionType.tvshows, + CollectionType.playlists + }; + + return view?.ViewType is not null && collectionStripViewTypes.Contains(view.ViewType.Value); + } + + protected override string CreateImage(BaseItem item, IReadOnlyCollection<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + { + if (itemsWithImages.Count == 0) + { + return null; + } + + var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png"); + + return CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540); + } + } +} diff --git a/Emby.Server.Implementations/Images/FolderImageProvider.cs b/Emby.Server.Implementations/Images/FolderImageProvider.cs new file mode 100644 index 00000000..90f7568a --- /dev/null +++ b/Emby.Server.Implementations/Images/FolderImageProvider.cs @@ -0,0 +1,35 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.Images +{ + public class FolderImageProvider : BaseFolderImageProvider<Folder> + { + public FolderImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) + : base(fileSystem, providerManager, applicationPaths, imageProcessor, libraryManager) + { + } + + protected override bool Supports(BaseItem item) + { + if (item is PhotoAlbum || item is MusicAlbum) + { + return false; + } + + if (item is Folder && item.IsTopParent) + { + return false; + } + + return true; + } + } +} diff --git a/Emby.Server.Implementations/Images/GenreImageProvider.cs b/Emby.Server.Implementations/Images/GenreImageProvider.cs new file mode 100644 index 00000000..706de60a --- /dev/null +++ b/Emby.Server.Implementations/Images/GenreImageProvider.cs @@ -0,0 +1,52 @@ +#pragma warning disable CS1591 + +using System.Collections.Generic; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Images +{ + /// <summary> + /// Class GenreImageProvider. + /// </summary> + public class GenreImageProvider : BaseDynamicImageProvider<Genre> + { + /// <summary> + /// The library manager. + /// </summary> + private readonly ILibraryManager _libraryManager; + + public GenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor) + { + _libraryManager = libraryManager; + } + + /// <summary> + /// Get children objects used to create an genre image. + /// </summary> + /// <param name="item">The genre used to create the image.</param> + /// <returns>Any relevant children objects.</returns> + protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item) + { + return _libraryManager.GetItemList(new InternalItemsQuery + { + Genres = new[] { item.Name }, + IncludeItemTypes = new[] { BaseItemKind.Series, BaseItemKind.Movie }, + OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) }, + Limit = 4, + Recursive = true, + ImageTypes = new[] { ImageType.Primary }, + DtoOptions = new DtoOptions(false) + }); + } + } +} diff --git a/Emby.Server.Implementations/Images/MusicAlbumImageProvider.cs b/Emby.Server.Implementations/Images/MusicAlbumImageProvider.cs new file mode 100644 index 00000000..98e26a32 --- /dev/null +++ b/Emby.Server.Implementations/Images/MusicAlbumImageProvider.cs @@ -0,0 +1,30 @@ +#pragma warning disable CS1591 + +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.Images +{ + public class MusicAlbumImageProvider : BaseFolderImageProvider<MusicAlbum> + { + public MusicAlbumImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) + : base(fileSystem, providerManager, applicationPaths, imageProcessor, libraryManager) + { + } + + protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item) + { + var items = base.GetItemsWithImages(item); + + // Ignore any folders because they can have generated collages + return items.Where(i => i is not Folder).ToList(); + } + } +} diff --git a/Emby.Server.Implementations/Images/MusicGenreImageProvider.cs b/Emby.Server.Implementations/Images/MusicGenreImageProvider.cs new file mode 100644 index 00000000..c472623e --- /dev/null +++ b/Emby.Server.Implementations/Images/MusicGenreImageProvider.cs @@ -0,0 +1,60 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System.Collections.Generic; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Images +{ + /// <summary> + /// Class MusicGenreImageProvider. + /// </summary> + public class MusicGenreImageProvider : BaseDynamicImageProvider<MusicGenre> + { + /// <summary> + /// The library manager. + /// </summary> + private readonly ILibraryManager _libraryManager; + + public MusicGenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor) + { + _libraryManager = libraryManager; + } + + /// <summary> + /// Get children objects used to create an music genre image. + /// </summary> + /// <param name="item">The music genre used to create the image.</param> + /// <returns>Any relevant children objects.</returns> + protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item) + { + return _libraryManager.GetItemList(new InternalItemsQuery + { + Genres = new[] { item.Name }, + IncludeItemTypes = new[] + { + BaseItemKind.MusicAlbum, + BaseItemKind.MusicVideo, + BaseItemKind.Audio + }, + OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) }, + Limit = 4, + Recursive = true, + ImageTypes = new[] { ImageType.Primary }, + DtoOptions = new DtoOptions(false) + }); + } + } +} diff --git a/Emby.Server.Implementations/Images/PhotoAlbumImageProvider.cs b/Emby.Server.Implementations/Images/PhotoAlbumImageProvider.cs new file mode 100644 index 00000000..1ddb4c75 --- /dev/null +++ b/Emby.Server.Implementations/Images/PhotoAlbumImageProvider.cs @@ -0,0 +1,19 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.Images +{ + public class PhotoAlbumImageProvider : BaseFolderImageProvider<PhotoAlbum> + { + public PhotoAlbumImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) + : base(fileSystem, providerManager, applicationPaths, imageProcessor, libraryManager) + { + } + } +} diff --git a/Emby.Server.Implementations/Images/PlaylistImageProvider.cs b/Emby.Server.Implementations/Images/PlaylistImageProvider.cs new file mode 100644 index 00000000..3326d21a --- /dev/null +++ b/Emby.Server.Implementations/Images/PlaylistImageProvider.cs @@ -0,0 +1,65 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Playlists; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.Images +{ + public class PlaylistImageProvider : BaseDynamicImageProvider<Playlist> + { + public PlaylistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) : base(fileSystem, providerManager, applicationPaths, imageProcessor) + { + } + + protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item) + { + var playlist = (Playlist)item; + + return playlist.GetManageableItems() + .Select(i => + { + var subItem = i.Item2; + + if (subItem is Episode episode) + { + var series = episode.Series; + if (series is not null && series.HasImage(ImageType.Primary)) + { + return series; + } + } + + if (subItem.HasImage(ImageType.Primary)) + { + return subItem; + } + + var parent = subItem.GetOwner() ?? subItem.GetParent(); + + if (parent is not null && parent.HasImage(ImageType.Primary)) + { + if (parent is MusicAlbum) + { + return parent; + } + } + + return null; + }) + .Where(i => i is not null) + .DistinctBy(x => x.Id) + .ToList(); + } + } +} diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs new file mode 100644 index 00000000..ca0744a1 --- /dev/null +++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -0,0 +1,69 @@ +using System; +using System.IO; +using Emby.Naming.Audio; +using Emby.Naming.Common; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.Library +{ + /// <summary> + /// Provides the core resolver ignore rules. + /// </summary> + public class CoreResolutionIgnoreRule : IResolverIgnoreRule + { + private readonly NamingOptions _namingOptions; + private readonly IServerApplicationPaths _serverApplicationPaths; + + /// <summary> + /// Initializes a new instance of the <see cref="CoreResolutionIgnoreRule"/> class. + /// </summary> + /// <param name="namingOptions">The naming options.</param> + /// <param name="serverApplicationPaths">The server application paths.</param> + public CoreResolutionIgnoreRule(NamingOptions namingOptions, IServerApplicationPaths serverApplicationPaths) + { + _namingOptions = namingOptions; + _serverApplicationPaths = serverApplicationPaths; + } + + /// <inheritdoc /> + public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem? parent) + { + // Don't ignore application folders + if (fileInfo.FullName.Contains(_serverApplicationPaths.RootFolderPath, StringComparison.InvariantCulture)) + { + return false; + } + + if (IgnorePatterns.ShouldIgnore(fileInfo.FullName)) + { + return true; + } + + // Don't ignore top level folders + if (fileInfo.IsDirectory + && (parent is AggregateFolder || (parent?.IsTopParent ?? false))) + { + return false; + } + + if (parent is null) + { + return false; + } + + if (fileInfo.IsDirectory) + { + // Ignore extras for unsupported types + return _namingOptions.AllExtrasTypesFolderNames.ContainsKey(fileInfo.Name) + && parent is not UserRootFolder; + } + + // Don't resolve theme songs + return Path.GetFileNameWithoutExtension(fileInfo.Name.AsSpan()).Equals(BaseItem.ThemeSongFileName, StringComparison.Ordinal) + && AudioFileParser.IsAudioFile(fileInfo.Name, _namingOptions); + } + } +} diff --git a/Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs b/Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs new file mode 100644 index 00000000..ef5d24c7 --- /dev/null +++ b/Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs @@ -0,0 +1,140 @@ +using System; +using System.IO; +using System.Text.RegularExpressions; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.Library; + +/// <summary> +/// Resolver rule class for ignoring files via .ignore. +/// </summary> +public class DotIgnoreIgnoreRule : IResolverIgnoreRule +{ + private static readonly bool IsWindows = OperatingSystem.IsWindows(); + + private static FileInfo? FindIgnoreFile(DirectoryInfo directory) + { + for (var current = directory; current is not null; current = current.Parent) + { + var ignorePath = Path.Join(current.FullName, ".ignore"); + if (File.Exists(ignorePath)) + { + return new FileInfo(ignorePath); + } + } + + return null; + } + + /// <inheritdoc /> + public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem? parent) => IsIgnored(fileInfo, parent); + + /// <summary> + /// Checks whether or not the file is ignored. + /// </summary> + /// <param name="fileInfo">The file information.</param> + /// <param name="parent">The parent BaseItem.</param> + /// <returns>True if the file should be ignored.</returns> + public static bool IsIgnored(FileSystemMetadata fileInfo, BaseItem? parent) + { + var searchDirectory = fileInfo.IsDirectory + ? new DirectoryInfo(fileInfo.FullName) + : new DirectoryInfo(Path.GetDirectoryName(fileInfo.FullName) ?? string.Empty); + + if (string.IsNullOrEmpty(searchDirectory.FullName)) + { + return false; + } + + var ignoreFile = FindIgnoreFile(searchDirectory); + if (ignoreFile is null) + { + return false; + } + + // Fast path in case the ignore files isn't a symlink and is empty + if (ignoreFile.LinkTarget is null && ignoreFile.Length == 0) + { + // Ignore directory if we just have the file + return true; + } + + var content = GetFileContent(ignoreFile); + return string.IsNullOrWhiteSpace(content) + || CheckIgnoreRules(fileInfo.FullName, content, fileInfo.IsDirectory); + } + + private static bool CheckIgnoreRules(string path, string ignoreFileContent, bool isDirectory) + { + // If file has content, base ignoring off the content .gitignore-style rules + var rules = ignoreFileContent.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + return CheckIgnoreRules(path, rules, isDirectory); + } + + /// <summary> + /// Checks whether a path should be ignored based on an array of ignore rules. + /// </summary> + /// <param name="path">The path to check.</param> + /// <param name="rules">The array of ignore rules.</param> + /// <param name="isDirectory">Whether the path is a directory.</param> + /// <returns>True if the path should be ignored.</returns> + internal static bool CheckIgnoreRules(string path, string[] rules, bool isDirectory) + => CheckIgnoreRules(path, rules, isDirectory, IsWindows); + + /// <summary> + /// Checks whether a path should be ignored based on an array of ignore rules. + /// </summary> + /// <param name="path">The path to check.</param> + /// <param name="rules">The array of ignore rules.</param> + /// <param name="isDirectory">Whether the path is a directory.</param> + /// <param name="normalizePath">Whether to normalize backslashes to forward slashes (for Windows paths).</param> + /// <returns>True if the path should be ignored.</returns> + internal static bool CheckIgnoreRules(string path, string[] rules, bool isDirectory, bool normalizePath) + { + var ignore = new Ignore.Ignore(); + + // Add each rule individually to catch and skip invalid patterns + var validRulesAdded = 0; + foreach (var rule in rules) + { + try + { + ignore.Add(rule); + validRulesAdded++; + } + catch (RegexParseException) + { + // Ignore invalid patterns + } + } + + // If no valid rules were added, fall back to ignoring everything (like an empty .ignore file) + if (validRulesAdded == 0) + { + return true; + } + + // Mitigate the problem of the Ignore library not handling Windows paths correctly. + // See https://github.com/jellyfin/jellyfin/issues/15484 + var pathToCheck = normalizePath ? path.NormalizePath('/') : path; + + // Add trailing slash for directories to match "folder/" + if (isDirectory) + { + pathToCheck = string.Concat(pathToCheck.AsSpan().TrimEnd('/'), "/"); + } + + return ignore.IsIgnored(pathToCheck); + } + + private static string GetFileContent(FileInfo ignoreFile) + { + ignoreFile = FileSystemHelper.ResolveLinkTarget(ignoreFile, returnFinalTarget: true) ?? ignoreFile; + return ignoreFile.Exists + ? File.ReadAllText(ignoreFile.FullName) + : string.Empty; + } +} diff --git a/Emby.Server.Implementations/Library/ExternalDataManager.cs b/Emby.Server.Implementations/Library/ExternalDataManager.cs new file mode 100644 index 00000000..4ad0f999 --- /dev/null +++ b/Emby.Server.Implementations/Library/ExternalDataManager.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Chapters; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.MediaSegments; +using MediaBrowser.Controller.Trickplay; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library; + +/// <summary> +/// IExternalDataManager implementation. +/// </summary> +public class ExternalDataManager : IExternalDataManager +{ + private readonly IKeyframeManager _keyframeManager; + private readonly IMediaSegmentManager _mediaSegmentManager; + private readonly IPathManager _pathManager; + private readonly ITrickplayManager _trickplayManager; + private readonly IChapterManager _chapterManager; + private readonly ILogger<ExternalDataManager> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="ExternalDataManager"/> class. + /// </summary> + /// <param name="keyframeManager">The keyframe manager.</param> + /// <param name="mediaSegmentManager">The media segment manager.</param> + /// <param name="pathManager">The path manager.</param> + /// <param name="trickplayManager">The trickplay manager.</param> + /// <param name="chapterManager">The chapter manager.</param> + /// <param name="logger">The logger.</param> + public ExternalDataManager( + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager, + IPathManager pathManager, + ITrickplayManager trickplayManager, + IChapterManager chapterManager, + ILogger<ExternalDataManager> logger) + { + _keyframeManager = keyframeManager; + _mediaSegmentManager = mediaSegmentManager; + _pathManager = pathManager; + _trickplayManager = trickplayManager; + _chapterManager = chapterManager; + _logger = logger; + } + + /// <inheritdoc/> + public async Task DeleteExternalItemDataAsync(BaseItem item, CancellationToken cancellationToken) + { + var validPaths = _pathManager.GetExtractedDataPaths(item).Where(Directory.Exists).ToList(); + var itemId = item.Id; + if (validPaths.Count > 0) + { + foreach (var path in validPaths) + { + try + { + Directory.Delete(path, true); + } + catch (Exception ex) + { + _logger.LogWarning("Unable to prune external item data at {Path}: {Exception}", path, ex); + } + } + } + + await _keyframeManager.DeleteKeyframeDataAsync(itemId, cancellationToken).ConfigureAwait(false); + await _mediaSegmentManager.DeleteSegmentsAsync(itemId, cancellationToken).ConfigureAwait(false); + await _trickplayManager.DeleteTrickplayDataAsync(itemId, cancellationToken).ConfigureAwait(false); + await _chapterManager.DeleteChapterDataAsync(itemId, cancellationToken).ConfigureAwait(false); + } +} diff --git a/Emby.Server.Implementations/Library/IgnorePatterns.cs b/Emby.Server.Implementations/Library/IgnorePatterns.cs new file mode 100644 index 00000000..59ccb9e2 --- /dev/null +++ b/Emby.Server.Implementations/Library/IgnorePatterns.cs @@ -0,0 +1,136 @@ +using System; +using DotNet.Globbing; + +namespace Emby.Server.Implementations.Library +{ + /// <summary> + /// Glob patterns for files to ignore. + /// </summary> + public static class IgnorePatterns + { + /// <summary> + /// Files matching these glob patterns will be ignored. + /// </summary> + private static readonly string[] _patterns = + { + "**/small.jpg", + "**/albumart.jpg", + + // We have neither non-greedy matching or character group repetitions, working around that here. + // https://github.com/dazinator/DotNet.Glob#patterns + // .*/sample\..{1,5} + "**/sample.?", + "**/sample.??", + "**/sample.???", // Matches sample.mkv + "**/sample.????", // Matches sample.webm + "**/sample.?????", + "**/*.sample.?", + "**/*.sample.??", + "**/*.sample.???", + "**/*.sample.????", + "**/*.sample.?????", + "**/sample/*", + + // Directories + "**/metadata/**", + "**/metadata", + "**/ps3_update/**", + "**/ps3_update", + "**/ps3_vprm/**", + "**/ps3_vprm", + "**/extrafanart/**", + "**/extrafanart", + "**/extrathumbs/**", + "**/extrathumbs", + "**/.actors/**", + "**/.actors", + "**/.wd_tv/**", + "**/.wd_tv", + "**/lost+found/**", + "**/lost+found", + "**/subs/**", + "**/subs", + "**/.snapshots/**", + "**/.snapshots", + "**/.snapshot/**", + "**/.snapshot", + + // Trickplay files + "**/*.trickplay", + "**/*.trickplay/**", + + // WMC temp recording directories that will constantly be written to + "**/TempRec/**", + "**/TempRec", + "**/TempSBE/**", + "**/TempSBE", + + // Synology + "**/eaDir/**", + "**/eaDir", + "**/@eaDir/**", + "**/@eaDir", + "**/#recycle/**", + "**/#recycle", + + // Qnap + "**/@Recycle/**", + "**/@Recycle", + "**/.@__thumb/**", + "**/.@__thumb", + "**/$RECYCLE.BIN/**", + "**/$RECYCLE.BIN", + "**/System Volume Information/**", + "**/System Volume Information", + "**/.grab/**", + "**/.grab", + + // Unix hidden files + "**/.*", + + // Mac - if you ever remove the above. + // "**/._*", + // "**/.DS_Store", + + // thumbs.db + "**/thumbs.db", + + // bts sync files + "**/*.bts", + "**/*.sync", + + // zfs + "**/.zfs/**", + "**/.zfs" + }; + + private static readonly GlobOptions _globOptions = new GlobOptions + { + Evaluation = + { + CaseInsensitive = true + } + }; + + private static readonly Glob[] _globs = Array.ConvertAll(_patterns, p => Glob.Parse(p, _globOptions)); + + /// <summary> + /// Returns true if the supplied path should be ignored. + /// </summary> + /// <param name="path">The path to test.</param> + /// <returns>Whether to ignore the path.</returns> + public static bool ShouldIgnore(ReadOnlySpan<char> path) + { + int len = _globs.Length; + for (int i = 0; i < len; i++) + { + if (_globs[i].IsMatch(path)) + { + return true; + } + } + + return false; + } + } +} diff --git a/Emby.Server.Implementations/Library/KeyframeManager.cs b/Emby.Server.Implementations/Library/KeyframeManager.cs new file mode 100644 index 00000000..18f4ce04 --- /dev/null +++ b/Emby.Server.Implementations/Library/KeyframeManager.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.MediaEncoding.Keyframes; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Persistence; + +namespace Emby.Server.Implementations.Library; + +/// <summary> +/// Manager for Keyframe data. +/// </summary> +public class KeyframeManager : IKeyframeManager +{ + private readonly IKeyframeRepository _repository; + + /// <summary> + /// Initializes a new instance of the <see cref="KeyframeManager"/> class. + /// </summary> + /// <param name="repository">The keyframe repository.</param> + public KeyframeManager(IKeyframeRepository repository) + { + _repository = repository; + } + + /// <inheritdoc /> + public IReadOnlyList<KeyframeData> GetKeyframeData(Guid itemId) + { + return _repository.GetKeyframeData(itemId); + } + + /// <inheritdoc /> + public async Task SaveKeyframeDataAsync(Guid itemId, KeyframeData data, CancellationToken cancellationToken) + { + await _repository.SaveKeyframeDataAsync(itemId, data, cancellationToken).ConfigureAwait(false); + } + + /// <inheritdoc /> + public async Task DeleteKeyframeDataAsync(Guid itemId, CancellationToken cancellationToken) + { + await _repository.DeleteKeyframeDataAsync(itemId, cancellationToken).ConfigureAwait(false); + } +} diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs new file mode 100644 index 00000000..f7f5c387 --- /dev/null +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -0,0 +1,3389 @@ +#pragma warning disable CS1591 +#pragma warning disable CA5394 + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using BitFaster.Caching.Lru; +using Emby.Naming.Common; +using Emby.Naming.TV; +using Emby.Server.Implementations.Library.Resolvers; +using Emby.Server.Implementations.Library.Validators; +using Emby.Server.Implementations.Playlists; +using Emby.Server.Implementations.ScheduledTasks.Tasks; +using Emby.Server.Implementations.Sorting; +using Jellyfin.Data; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.MediaSegments; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Controller.Trickplay; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Library; +using MediaBrowser.Model.Querying; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using EpisodeInfo = Emby.Naming.TV.EpisodeInfo; +using Genre = MediaBrowser.Controller.Entities.Genre; +using Person = MediaBrowser.Controller.Entities.Person; +using VideoResolver = Emby.Naming.Video.VideoResolver; + +namespace Emby.Server.Implementations.Library +{ + /// <summary> + /// Class LibraryManager. + /// </summary> + public class LibraryManager : ILibraryManager + { + private const string ShortcutFileExtension = ".mblink"; + + private readonly ILogger<LibraryManager> _logger; + private readonly ITaskManager _taskManager; + private readonly IUserManager _userManager; + private readonly IUserDataManager _userDataManager; + private readonly IServerConfigurationManager _configurationManager; + private readonly Lazy<ILibraryMonitor> _libraryMonitorFactory; + private readonly Lazy<IProviderManager> _providerManagerFactory; + private readonly Lazy<IUserViewManager> _userViewManagerFactory; + private readonly IServerApplicationHost _appHost; + private readonly IMediaEncoder _mediaEncoder; + private readonly IFileSystem _fileSystem; + private readonly IItemRepository _itemRepository; + private readonly IImageProcessor _imageProcessor; + private readonly NamingOptions _namingOptions; + private readonly IPeopleRepository _peopleRepository; + private readonly ExtraResolver _extraResolver; + private readonly IPathManager _pathManager; + private readonly FastConcurrentLru<Guid, BaseItem> _cache; + + /// <summary> + /// The _root folder sync lock. + /// </summary> + private readonly Lock _rootFolderSyncLock = new(); + private readonly Lock _userRootFolderSyncLock = new(); + + private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24); + + /// <summary> + /// The _root folder. + /// </summary> + private volatile AggregateFolder? _rootFolder; + private volatile UserRootFolder? _userRootFolder; + + private bool _wizardCompleted; + + /// <summary> + /// Initializes a new instance of the <see cref="LibraryManager" /> class. + /// </summary> + /// <param name="appHost">The application host.</param> + /// <param name="loggerFactory">The logger factory.</param> + /// <param name="taskManager">The task manager.</param> + /// <param name="userManager">The user manager.</param> + /// <param name="configurationManager">The configuration manager.</param> + /// <param name="userDataManager">The user data manager.</param> + /// <param name="libraryMonitorFactory">The library monitor.</param> + /// <param name="fileSystem">The file system.</param> + /// <param name="providerManagerFactory">The provider manager.</param> + /// <param name="userViewManagerFactory">The user view manager.</param> + /// <param name="mediaEncoder">The media encoder.</param> + /// <param name="itemRepository">The item repository.</param> + /// <param name="imageProcessor">The image processor.</param> + /// <param name="namingOptions">The naming options.</param> + /// <param name="directoryService">The directory service.</param> + /// <param name="peopleRepository">The people repository.</param> + /// <param name="pathManager">The path manager.</param> + public LibraryManager( + IServerApplicationHost appHost, + ILoggerFactory loggerFactory, + ITaskManager taskManager, + IUserManager userManager, + IServerConfigurationManager configurationManager, + IUserDataManager userDataManager, + Lazy<ILibraryMonitor> libraryMonitorFactory, + IFileSystem fileSystem, + Lazy<IProviderManager> providerManagerFactory, + Lazy<IUserViewManager> userViewManagerFactory, + IMediaEncoder mediaEncoder, + IItemRepository itemRepository, + IImageProcessor imageProcessor, + NamingOptions namingOptions, + IDirectoryService directoryService, + IPeopleRepository peopleRepository, + IPathManager pathManager) + { + _appHost = appHost; + _logger = loggerFactory.CreateLogger<LibraryManager>(); + _taskManager = taskManager; + _userManager = userManager; + _configurationManager = configurationManager; + _userDataManager = userDataManager; + _libraryMonitorFactory = libraryMonitorFactory; + _fileSystem = fileSystem; + _providerManagerFactory = providerManagerFactory; + _userViewManagerFactory = userViewManagerFactory; + _mediaEncoder = mediaEncoder; + _itemRepository = itemRepository; + _imageProcessor = imageProcessor; + + _cache = new FastConcurrentLru<Guid, BaseItem>(_configurationManager.Configuration.CacheSize); + + _namingOptions = namingOptions; + _peopleRepository = peopleRepository; + _pathManager = pathManager; + _extraResolver = new ExtraResolver(loggerFactory.CreateLogger<ExtraResolver>(), namingOptions, directoryService); + + _configurationManager.ConfigurationUpdated += ConfigurationUpdated; + + RecordConfigurationValues(_configurationManager.Configuration); + } + + /// <summary> + /// Occurs when [item added]. + /// </summary> + public event EventHandler<ItemChangeEventArgs>? ItemAdded; + + /// <summary> + /// Occurs when [item updated]. + /// </summary> + public event EventHandler<ItemChangeEventArgs>? ItemUpdated; + + /// <summary> + /// Occurs when [item removed]. + /// </summary> + public event EventHandler<ItemChangeEventArgs>? ItemRemoved; + + /// <summary> + /// Gets the root folder. + /// </summary> + /// <value>The root folder.</value> + public AggregateFolder RootFolder + { + get + { + if (_rootFolder is null) + { + lock (_rootFolderSyncLock) + { + _rootFolder ??= CreateRootFolder(); + } + } + + return _rootFolder; + } + } + + private ILibraryMonitor LibraryMonitor => _libraryMonitorFactory.Value; + + private IProviderManager ProviderManager => _providerManagerFactory.Value; + + private IUserViewManager UserViewManager => _userViewManagerFactory.Value; + + /// <summary> + /// Gets or sets the postscan tasks. + /// </summary> + /// <value>The postscan tasks.</value> + private ILibraryPostScanTask[] PostScanTasks { get; set; } = []; + + /// <summary> + /// Gets or sets the intro providers. + /// </summary> + /// <value>The intro providers.</value> + private IIntroProvider[] IntroProviders { get; set; } = []; + + /// <summary> + /// Gets or sets the list of entity resolution ignore rules. + /// </summary> + /// <value>The entity resolution ignore rules.</value> + private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } = []; + + /// <summary> + /// Gets or sets the list of currently registered entity resolvers. + /// </summary> + /// <value>The entity resolvers enumerable.</value> + private IItemResolver[] EntityResolvers { get; set; } = []; + + private IMultiItemResolver[] MultiItemResolvers { get; set; } = []; + + /// <summary> + /// Gets or sets the comparers. + /// </summary> + /// <value>The comparers.</value> + private IBaseItemComparer[] Comparers { get; set; } = []; + + public bool IsScanRunning { get; private set; } + + /// <summary> + /// Adds the parts. + /// </summary> + /// <param name="rules">The rules.</param> + /// <param name="resolvers">The resolvers.</param> + /// <param name="introProviders">The intro providers.</param> + /// <param name="itemComparers">The item comparers.</param> + /// <param name="postScanTasks">The post scan tasks.</param> + public void AddParts( + IEnumerable<IResolverIgnoreRule> rules, + IEnumerable<IItemResolver> resolvers, + IEnumerable<IIntroProvider> introProviders, + IEnumerable<IBaseItemComparer> itemComparers, + IEnumerable<ILibraryPostScanTask> postScanTasks) + { + EntityResolutionIgnoreRules = rules.ToArray(); + EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray(); + MultiItemResolvers = EntityResolvers.OfType<IMultiItemResolver>().ToArray(); + IntroProviders = introProviders.ToArray(); + Comparers = itemComparers.ToArray(); + PostScanTasks = postScanTasks.ToArray(); + } + + /// <summary> + /// Records the configuration values. + /// </summary> + /// <param name="configuration">The configuration.</param> + private void RecordConfigurationValues(ServerConfiguration configuration) + { + _wizardCompleted = configuration.IsStartupWizardCompleted; + } + + /// <summary> + /// Configurations the updated. + /// </summary> + /// <param name="sender">The sender.</param> + /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> + private void ConfigurationUpdated(object? sender, EventArgs e) + { + var config = _configurationManager.Configuration; + + var wizardChanged = config.IsStartupWizardCompleted != _wizardCompleted; + + RecordConfigurationValues(config); + + if (wizardChanged) + { + _taskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>(); + } + } + + public void RegisterItem(BaseItem item) + { + ArgumentNullException.ThrowIfNull(item); + + if (item is IItemByName) + { + if (item is not MusicArtist) + { + return; + } + } + else if (!item.IsFolder) + { + if (item is not Video && item is not LiveTvChannel) + { + return; + } + } + + _cache.AddOrUpdate(item.Id, item); + } + + public void DeleteItem(BaseItem item, DeleteOptions options) + { + DeleteItem(item, options, false); + } + + public void DeleteItem(BaseItem item, DeleteOptions options, bool notifyParentItem) + { + ArgumentNullException.ThrowIfNull(item); + + var parent = item.GetOwner() ?? item.GetParent(); + + DeleteItem(item, options, parent, notifyParentItem); + } + + public void DeleteItemsUnsafeFast(IEnumerable<BaseItem> items) + { + var pathMaps = items.Select(e => (Item: e, InternalPath: GetInternalMetadataPaths(e), DeletePaths: e.GetDeletePaths())).ToArray(); + + foreach (var (item, internalPaths, pathsToDelete) in pathMaps) + { + foreach (var metadataPath in internalPaths) + { + if (!Directory.Exists(metadataPath)) + { + continue; + } + + _logger.LogDebug( + "Deleting metadata path, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", + item.GetType().Name, + item.Name ?? "Unknown name", + metadataPath, + item.Id); + + try + { + Directory.Delete(metadataPath, true); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting {MetadataPath}", metadataPath); + } + } + + foreach (var fileSystemInfo in pathsToDelete) + { + DeleteItemPath(item, false, fileSystemInfo); + } + } + + _itemRepository.DeleteItem([.. pathMaps.Select(f => f.Item.Id)]); + } + + public void DeleteItem(BaseItem item, DeleteOptions options, BaseItem parent, bool notifyParentItem) + { + ArgumentNullException.ThrowIfNull(item); + + if (item.SourceType == SourceType.Channel) + { + if (options.DeleteFromExternalProvider) + { + try + { + BaseItem.ChannelManager.DeleteItem(item).GetAwaiter().GetResult(); + } + catch (ArgumentException) + { + // channel no longer installed + } + } + + options.DeleteFileLocation = false; + } + + if (item is LiveTvProgram) + { + _logger.LogDebug( + "Removing item, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", + item.GetType().Name, + item.Name ?? "Unknown name", + item.Path ?? string.Empty, + item.Id); + } + else + { + _logger.LogInformation( + "Removing item, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", + item.GetType().Name, + item.Name ?? "Unknown name", + item.Path ?? string.Empty, + item.Id); + } + + var children = item.IsFolder + ? ((Folder)item).GetRecursiveChildren(false) + : []; + + foreach (var metadataPath in GetMetadataPaths(item, children)) + { + if (!Directory.Exists(metadataPath)) + { + continue; + } + + _logger.LogDebug( + "Deleting metadata path, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", + item.GetType().Name, + item.Name ?? "Unknown name", + metadataPath, + item.Id); + + try + { + Directory.Delete(metadataPath, true); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting {MetadataPath}", metadataPath); + } + } + + if ((options.DeleteFileLocation && item.IsFileProtocol) || IsInternalItem(item)) + { + // Assume only the first is required + // Add this flag to GetDeletePaths if required in the future + var isRequiredForDelete = true; + + foreach (var fileSystemInfo in item.GetDeletePaths()) + { + DeleteItemPath(item, isRequiredForDelete, fileSystemInfo); + + isRequiredForDelete = false; + } + } + + item.SetParent(null); + + _itemRepository.DeleteItem([item.Id, .. children.Select(f => f.Id)]); + _cache.TryRemove(item.Id, out _); + foreach (var child in children) + { + _cache.TryRemove(child.Id, out _); + } + + if (parent is Folder folder) + { + folder.Children = null; + folder.UserData = null; + } + + ReportItemRemoved(item, parent); + } + + private void DeleteItemPath(BaseItem item, bool isRequiredForDelete, FileSystemMetadata fileSystemInfo) + { + if (Directory.Exists(fileSystemInfo.FullName) || File.Exists(fileSystemInfo.FullName)) + { + try + { + _logger.LogInformation( + "Deleting item path, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", + item.GetType().Name, + item.Name ?? "Unknown name", + fileSystemInfo.FullName, + item.Id); + + if (fileSystemInfo.IsDirectory) + { + Directory.Delete(fileSystemInfo.FullName, true); + } + else + { + File.Delete(fileSystemInfo.FullName); + } + } + catch (DirectoryNotFoundException) + { + _logger.LogInformation( + "Directory not found, only removing from database, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", + item.GetType().Name, + item.Name ?? "Unknown name", + fileSystemInfo.FullName, + item.Id); + } + catch (FileNotFoundException) + { + _logger.LogInformation( + "File not found, only removing from database, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", + item.GetType().Name, + item.Name ?? "Unknown name", + fileSystemInfo.FullName, + item.Id); + } + catch (IOException) + { + if (isRequiredForDelete) + { + throw; + } + } + catch (UnauthorizedAccessException) + { + if (isRequiredForDelete) + { + throw; + } + } + } + } + + private bool IsInternalItem(BaseItem item) + { + if (!item.IsFileProtocol) + { + return false; + } + + var pathToCheck = item switch + { + Genre => _configurationManager.ApplicationPaths.GenrePath, + MusicArtist => _configurationManager.ApplicationPaths.ArtistsPath, + MusicGenre => _configurationManager.ApplicationPaths.MusicGenrePath, + Person => _configurationManager.ApplicationPaths.PeoplePath, + Studio => _configurationManager.ApplicationPaths.StudioPath, + Year => _configurationManager.ApplicationPaths.YearPath, + _ => null + }; + + var itemPath = item.Path; + if (!string.IsNullOrEmpty(pathToCheck) && !string.IsNullOrEmpty(itemPath)) + { + var cleanPath = _fileSystem.GetValidFilename(itemPath); + var cleanCheckPath = _fileSystem.GetValidFilename(pathToCheck); + + return cleanPath.StartsWith(cleanCheckPath, StringComparison.Ordinal); + } + + return false; + } + + private List<string> GetMetadataPaths(BaseItem item, IEnumerable<BaseItem> children) + { + var list = GetInternalMetadataPaths(item); + foreach (var child in children) + { + list.AddRange(GetInternalMetadataPaths(child)); + } + + return list; + } + + private List<string> GetInternalMetadataPaths(BaseItem item) + { + var list = new List<string> + { + item.GetInternalMetadataPath() + }; + + if (item is Video video) + { + // Trickplay + list.Add(_pathManager.GetTrickplayDirectory(video)); + + // Subtitles and attachments + foreach (var mediaSource in item.GetMediaSources(false)) + { + var subtitleFolder = _pathManager.GetSubtitleFolderPath(mediaSource.Id); + if (subtitleFolder is not null) + { + list.Add(subtitleFolder); + } + + var attachmentFolder = _pathManager.GetAttachmentFolderPath(mediaSource.Id); + if (attachmentFolder is not null) + { + list.Add(attachmentFolder); + } + } + } + + return list; + } + + /// <summary> + /// Resolves the item. + /// </summary> + /// <param name="args">The args.</param> + /// <param name="resolvers">The resolvers.</param> + /// <returns>BaseItem.</returns> + private BaseItem? ResolveItem(ItemResolveArgs args, IItemResolver[]? resolvers) + { + var item = (resolvers ?? EntityResolvers).Select(r => Resolve(args, r)) + .FirstOrDefault(i => i is not null); + + if (item is not null) + { + ResolverHelper.SetInitialItemValues(item, args, _fileSystem, this); + } + + return item; + } + + private BaseItem? Resolve(ItemResolveArgs args, IItemResolver resolver) + { + try + { + return resolver.ResolvePath(args); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in {Resolver} resolving {Path}", resolver.GetType().Name, args.Path); + return null; + } + } + + public Guid GetNewItemId(string key, Type type) + { + return GetNewItemIdInternal(key, type, false); + } + + private Guid GetNewItemIdInternal(string key, Type type, bool forceCaseInsensitive) + { + ArgumentException.ThrowIfNullOrEmpty(key); + ArgumentNullException.ThrowIfNull(type); + + string programDataPath = _configurationManager.ApplicationPaths.ProgramDataPath; + if (key.StartsWith(programDataPath, StringComparison.Ordinal)) + { + // Try to normalize paths located underneath program-data in an attempt to make them more portable + key = key.Substring(programDataPath.Length) + .TrimStart('/', '\\') + .Replace('/', '\\'); + } + + if (forceCaseInsensitive || !_configurationManager.Configuration.EnableCaseSensitiveItemIds) + { + key = key.ToLowerInvariant(); + } + + key = type.FullName + key; + + return key.GetMD5(); + } + + public BaseItem? ResolvePath(FileSystemMetadata fileInfo, Folder? parent = null, IDirectoryService? directoryService = null) + => ResolvePath(fileInfo, directoryService ?? new DirectoryService(_fileSystem), null, parent); + + private BaseItem? ResolvePath( + FileSystemMetadata fileInfo, + IDirectoryService directoryService, + IItemResolver[]? resolvers, + Folder? parent = null, + CollectionType? collectionType = null, + LibraryOptions? libraryOptions = null) + { + ArgumentNullException.ThrowIfNull(fileInfo); + + var fullPath = fileInfo.FullName; + + if (collectionType is null && parent is not null) + { + collectionType = GetContentTypeOverride(fullPath, true); + } + + var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, this) + { + Parent = parent, + FileInfo = fileInfo, + CollectionType = collectionType, + LibraryOptions = libraryOptions + }; + + // Return null if ignore rules deem that we should do so + if (IgnoreFile(args.FileInfo, args.Parent)) + { + return null; + } + + // Gather child folder and files + if (args.IsDirectory) + { + var isPhysicalRoot = args.IsPhysicalRoot; + + // When resolving the root, we need it's grandchildren (children of user views) + var flattenFolderDepth = isPhysicalRoot ? 2 : 0; + + FileSystemMetadata[] files; + var isVf = args.IsVf; + + try + { + files = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, _fileSystem, _appHost, _logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || isVf); + } + catch (Exception ex) + { + if (parent is not null && parent.IsPhysicalRoot) + { + _logger.LogError(ex, "Error in GetFilteredFileSystemEntries isPhysicalRoot: {0} IsVf: {1}", isPhysicalRoot, isVf); + + files = []; + } + else + { + throw; + } + } + + // Need to remove sub-paths that may have been resolved from shortcuts + // Example: if \\server\movies exists, then strip out \\server\movies\action + if (isPhysicalRoot) + { + files = NormalizeRootPathList(files).ToArray(); + } + + args.FileSystemChildren = files; + } + + // Filter content based on ignore rules + if (args.IsDirectory) + { + var filtered = args.GetActualFileSystemChildren().ToArray(); + args.FileSystemChildren = filtered ?? []; + } + + return ResolveItem(args, resolvers); + } + + public bool IgnoreFile(FileSystemMetadata file, BaseItem? parent) + => EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent)); + + public List<FileSystemMetadata> NormalizeRootPathList(IEnumerable<FileSystemMetadata> paths) + { + var originalList = paths.ToList(); + + var list = originalList.Where(i => i.IsDirectory) + .Select(i => Path.TrimEndingDirectorySeparator(i.FullName)) + .Distinct() + .ToList(); + + var dupes = list.Where(subPath => !subPath.EndsWith(":\\", StringComparison.Ordinal) && list.Any(i => _fileSystem.ContainsSubPath(i, subPath))) + .ToList(); + + foreach (var dupe in dupes) + { + _logger.LogInformation("Found duplicate path: {0}", dupe); + } + + var newList = list.Except(dupes, StringComparer.Ordinal).Select(_fileSystem.GetDirectoryInfo).ToList(); + newList.AddRange(originalList.Where(i => !i.IsDirectory)); + return newList; + } + + public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, CollectionType? collectionType = null) + { + return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers); + } + + public IEnumerable<BaseItem> ResolvePaths( + IEnumerable<FileSystemMetadata> files, + IDirectoryService directoryService, + Folder parent, + LibraryOptions libraryOptions, + CollectionType? collectionType, + IItemResolver[] resolvers) + { + var fileList = files.Where(i => !IgnoreFile(i, parent)).ToList(); + + if (parent is not null) + { + var multiItemResolvers = resolvers is null ? MultiItemResolvers : resolvers.OfType<IMultiItemResolver>(); + + foreach (var resolver in multiItemResolvers) + { + var result = resolver.ResolveMultiple(parent, fileList, collectionType, directoryService); + + if (result?.Items.Count > 0) + { + var items = result.Items; + items.RemoveAll(item => !ResolverHelper.SetInitialItemValues(item, parent, this, directoryService)); + items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions)); + return items; + } + } + } + + return ResolveFileList(fileList, directoryService, parent, collectionType, resolvers, libraryOptions); + } + + private IEnumerable<BaseItem> ResolveFileList( + IReadOnlyList<FileSystemMetadata> fileList, + IDirectoryService directoryService, + Folder? parent, + CollectionType? collectionType, + IItemResolver[]? resolvers, + LibraryOptions libraryOptions) + { + // Given that fileList is a list we can save enumerator allocations by indexing + for (var i = 0; i < fileList.Count; i++) + { + var file = fileList[i]; + BaseItem? result = null; + try + { + result = ResolvePath(file, directoryService, resolvers, parent, collectionType, libraryOptions); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error resolving path {Path}", file.FullName); + } + + if (result is not null) + { + yield return result; + } + } + } + + /// <summary> + /// Creates the root media folder. + /// </summary> + /// <returns>AggregateFolder.</returns> + /// <exception cref="InvalidOperationException">Cannot create the root folder until plugins have loaded.</exception> + public AggregateFolder CreateRootFolder() + { + var rootFolderPath = _configurationManager.ApplicationPaths.RootFolderPath; + + var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ?? + (ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath)) as Folder ?? throw new InvalidOperationException("Something went very wong")) + .DeepCopy<Folder, AggregateFolder>(); + + // In case program data folder was moved + if (!string.Equals(rootFolder.Path, rootFolderPath, StringComparison.Ordinal)) + { + _logger.LogInformation("Resetting root folder path to {0}", rootFolderPath); + rootFolder.Path = rootFolderPath; + } + + // Add in the plug-in folders + var path = Path.Combine(_configurationManager.ApplicationPaths.DataPath, "playlists"); + + var info = Directory.CreateDirectory(path); + Folder folder = new PlaylistsFolder + { + Path = path, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, + }; + + if (folder.Id.IsEmpty()) + { + folder.Id = GetNewItemId(folder.Path, folder.GetType()); + } + + var dbItem = GetItemById(folder.Id) as BasePluginFolder; + + if (dbItem is not null && string.Equals(dbItem.Path, folder.Path, StringComparison.OrdinalIgnoreCase)) + { + folder = dbItem; + } + + if (!folder.ParentId.Equals(rootFolder.Id)) + { + rootFolder.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, CancellationToken.None).GetAwaiter().GetResult(); + folder.ParentId = rootFolder.Id; + folder.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, CancellationToken.None).GetAwaiter().GetResult(); + } + + rootFolder.AddVirtualChild(folder); + + RegisterItem(folder); + + return rootFolder; + } + + public Folder GetUserRootFolder() + { + if (_userRootFolder is null) + { + lock (_userRootFolderSyncLock) + { + if (_userRootFolder is null) + { + var userRootPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; + + _logger.LogDebug("Creating userRootPath at {Path}", userRootPath); + Directory.CreateDirectory(userRootPath); + + var newItemId = GetNewItemId(userRootPath, typeof(UserRootFolder)); + UserRootFolder? tmpItem = null; + try + { + tmpItem = GetItemById(newItemId) as UserRootFolder; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error creating UserRootFolder {Path}", newItemId); + } + + if (tmpItem is null) + { + _logger.LogDebug("Creating new userRootFolder with DeepCopy"); + tmpItem = (ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath)) as Folder ?? throw new InvalidOperationException("Failed to get user root path")) + .DeepCopy<Folder, UserRootFolder>(); + } + + // In case program data folder was moved + if (!string.Equals(tmpItem.Path, userRootPath, StringComparison.Ordinal)) + { + _logger.LogInformation("Resetting user root folder path to {0}", userRootPath); + tmpItem.Path = userRootPath; + } + + _userRootFolder = tmpItem; + _logger.LogDebug("Setting userRootFolder: {Folder}", _userRootFolder); + } + } + } + + return _userRootFolder; + } + + /// <inheritdoc /> + public BaseItem? FindByPath(string path, bool? isFolder) + { + // If this returns multiple items it could be tricky figuring out which one is correct. + // In most cases, the newest one will be and the others obsolete but not yet cleaned up + ArgumentException.ThrowIfNullOrEmpty(path); + + var query = new InternalItemsQuery + { + Path = path, + IsFolder = isFolder, + OrderBy = [(ItemSortBy.DateCreated, SortOrder.Descending)], + Limit = 1, + DtoOptions = new DtoOptions(true) + }; + + return GetItemList(query) + .FirstOrDefault(); + } + + /// <inheritdoc /> + public Person? GetPerson(string name) + { + var path = Person.GetPath(name); + var id = GetItemByNameId<Person>(path); + if (GetItemById(id) is Person item) + { + return item; + } + + return null; + } + + /// <summary> + /// Gets the studio. + /// </summary> + /// <param name="name">The name.</param> + /// <returns>Task{Studio}.</returns> + public Studio GetStudio(string name) + { + return CreateItemByName<Studio>(Studio.GetPath, name, new DtoOptions(true)); + } + + public Guid GetStudioId(string name) + { + return GetItemByNameId<Studio>(Studio.GetPath(name)); + } + + public Guid GetGenreId(string name) + { + return GetItemByNameId<Genre>(Genre.GetPath(name)); + } + + public Guid GetMusicGenreId(string name) + { + return GetItemByNameId<MusicGenre>(MusicGenre.GetPath(name)); + } + + /// <summary> + /// Gets the genre. + /// </summary> + /// <param name="name">The name.</param> + /// <returns>Task{Genre}.</returns> + public Genre GetGenre(string name) + { + return CreateItemByName<Genre>(Genre.GetPath, name, new DtoOptions(true)); + } + + /// <summary> + /// Gets the music genre. + /// </summary> + /// <param name="name">The name.</param> + /// <returns>Task{MusicGenre}.</returns> + public MusicGenre GetMusicGenre(string name) + { + return CreateItemByName<MusicGenre>(MusicGenre.GetPath, name, new DtoOptions(true)); + } + + /// <summary> + /// Gets the year. + /// </summary> + /// <param name="value">The value.</param> + /// <returns>Task{Year}.</returns> + public Year GetYear(int value) + { + if (value <= 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "Years less than or equal to 0 are invalid."); + } + + var name = value.ToString(CultureInfo.InvariantCulture); + + return CreateItemByName<Year>(Year.GetPath, name, new DtoOptions(true)); + } + + /// <summary> + /// Gets a Genre. + /// </summary> + /// <param name="name">The name.</param> + /// <returns>Task{Genre}.</returns> + public MusicArtist GetArtist(string name) + { + return GetArtist(name, new DtoOptions(true)); + } + + public IReadOnlyDictionary<string, MusicArtist[]> GetArtists(IReadOnlyList<string> names) + { + return _itemRepository.FindArtists(names); + } + + public MusicArtist GetArtist(string name, DtoOptions options) + { + return CreateItemByName<MusicArtist>(MusicArtist.GetPath, name, options); + } + + private T CreateItemByName<T>(Func<string, string> getPathFn, string name, DtoOptions options) + where T : BaseItem, new() + { + if (typeof(T) == typeof(MusicArtist)) + { + var existing = GetItemList(new InternalItemsQuery + { + IncludeItemTypes = [BaseItemKind.MusicArtist], + Name = name, + UseRawName = true, + DtoOptions = options + }).Cast<MusicArtist>() + .OrderBy(i => i.IsAccessedByName ? 1 : 0) + .Cast<T>() + .FirstOrDefault(); + + if (existing is not null) + { + return existing; + } + } + + var path = getPathFn(name); + var id = GetItemByNameId<T>(path); + var item = GetItemById(id) as T; + if (item is null) + { + var info = Directory.CreateDirectory(path); + item = new T + { + Name = name, + Id = id, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, + Path = path + }; + + CreateItem(item, null); + } + + return item; + } + + private Guid GetItemByNameId<T>(string path) + where T : BaseItem, new() + { + var forceCaseInsensitiveId = _configurationManager.Configuration.EnableNormalizedItemByNameIds; + return GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId); + } + + /// <inheritdoc /> + public Task ValidatePeopleAsync(IProgress<double> progress, CancellationToken cancellationToken) + { + // Ensure the location is available. + Directory.CreateDirectory(_configurationManager.ApplicationPaths.PeoplePath); + + return new PeopleValidator(this, _logger, _fileSystem).ValidatePeople(cancellationToken, progress); + } + + /// <summary> + /// Reloads the root media folder. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task ValidateMediaLibrary(IProgress<double> progress, CancellationToken cancellationToken) + { + // Just run the scheduled task so that the user can see it + _taskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>(); + + return Task.CompletedTask; + } + + /// <summary> + /// Validates the media library internal. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken) + { + IsScanRunning = true; + LibraryMonitor.Stop(); + + try + { + await PerformLibraryValidation(progress, cancellationToken).ConfigureAwait(false); + } + finally + { + LibraryMonitor.Start(); + IsScanRunning = false; + } + } + + public async Task ValidateTopLibraryFolders(CancellationToken cancellationToken, bool removeRoot = false) + { + RootFolder.Children = null; + await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); + + // Start by just validating the children of the root, but go no further + await RootFolder.ValidateChildren( + new Progress<double>(), + new MetadataRefreshOptions(new DirectoryService(_fileSystem)), + recursive: false, + allowRemoveRoot: removeRoot, + cancellationToken: cancellationToken).ConfigureAwait(false); + + var rootFolder = GetUserRootFolder(); + rootFolder.Children = null; + + await rootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); + + await rootFolder.ValidateChildren( + new Progress<double>(), + new MetadataRefreshOptions(new DirectoryService(_fileSystem)), + recursive: false, + allowRemoveRoot: removeRoot, + cancellationToken: cancellationToken).ConfigureAwait(false); + + // Quickly scan CollectionFolders for changes + var toDelete = new List<Guid>(); + foreach (var child in rootFolder.Children!.OfType<Folder>()) + { + // If the user has somehow deleted the collection directory, remove the metadata from the database. + if (child is CollectionFolder collectionFolder && !Directory.Exists(collectionFolder.Path)) + { + toDelete.Add(collectionFolder.Id); + } + else + { + await child.RefreshMetadata(cancellationToken).ConfigureAwait(false); + } + } + + if (toDelete.Count > 0) + { + _itemRepository.DeleteItem(toDelete.ToArray()); + } + } + + private async Task PerformLibraryValidation(IProgress<double> progress, CancellationToken cancellationToken) + { + _logger.LogInformation("Validating media library"); + + await ValidateTopLibraryFolders(cancellationToken).ConfigureAwait(false); + + var innerProgress = new Progress<double>(pct => progress.Report(pct * 0.96)); + + // Validate the entire media library + await RootFolder.ValidateChildren(innerProgress, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true, cancellationToken: cancellationToken).ConfigureAwait(false); + + progress.Report(96); + + innerProgress = new Progress<double>(pct => progress.Report(96 + (pct * .04))); + + await RunPostScanTasks(innerProgress, cancellationToken).ConfigureAwait(false); + + progress.Report(100); + } + + /// <summary> + /// Runs the post scan tasks. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + private async Task RunPostScanTasks(IProgress<double> progress, CancellationToken cancellationToken) + { + var tasks = PostScanTasks.ToList(); + + var numComplete = 0; + var numTasks = tasks.Count; + + foreach (var task in tasks) + { + // Prevent access to modified closure + var currentNumComplete = numComplete; + + var innerProgress = new Progress<double>(pct => + { + double innerPercent = pct; + innerPercent /= 100; + innerPercent += currentNumComplete; + + innerPercent /= numTasks; + innerPercent *= 100; + + progress.Report(innerPercent); + }); + + _logger.LogDebug("Running post-scan task {0}", task.GetType().Name); + + try + { + await task.Run(innerProgress, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + _logger.LogInformation("Post-scan task cancelled: {0}", task.GetType().Name); + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error running post-scan task"); + } + + numComplete++; + double percent = numComplete; + percent /= numTasks; + progress.Report(percent * 100); + } + + _itemRepository.UpdateInheritedValues(); + + progress.Report(100); + } + + /// <summary> + /// Gets the default view. + /// </summary> + /// <returns>IEnumerable{VirtualFolderInfo}.</returns> + public List<VirtualFolderInfo> GetVirtualFolders() + { + return GetVirtualFolders(false); + } + + public List<VirtualFolderInfo> GetVirtualFolders(bool includeRefreshState) + { + _logger.LogDebug("Getting topLibraryFolders"); + var topLibraryFolders = GetUserRootFolder().Children.ToList(); + + _logger.LogDebug("Getting refreshQueue"); + var refreshQueue = includeRefreshState ? ProviderManager.GetRefreshQueue() : null; + + return _fileSystem.GetDirectoryPaths(_configurationManager.ApplicationPaths.DefaultUserViewsPath) + .Select(dir => GetVirtualFolderInfo(dir, topLibraryFolders, refreshQueue)) + .ToList(); + } + + private VirtualFolderInfo GetVirtualFolderInfo(string dir, List<BaseItem> allCollectionFolders, HashSet<Guid>? refreshQueue) + { + var info = new VirtualFolderInfo + { + Name = Path.GetFileName(dir), + + Locations = _fileSystem.GetFilePaths(dir, false) + .Where(i => Path.GetExtension(i.AsSpan()).Equals(ShortcutFileExtension, StringComparison.OrdinalIgnoreCase)) + .Select(i => + { + try + { + return _appHost.ExpandVirtualPath(_fileSystem.ResolveShortcut(i)); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error resolving shortcut file {File}", i); + return null; + } + }) + .Where(i => i is not null) + .Order() + .ToArray(), + + CollectionType = GetCollectionType(dir) + }; + + var libraryFolder = allCollectionFolders.FirstOrDefault(i => string.Equals(i.Path, dir, StringComparison.OrdinalIgnoreCase)); + if (libraryFolder is not null) + { + var libraryFolderId = libraryFolder.Id.ToString("N", CultureInfo.InvariantCulture); + info.ItemId = libraryFolderId; + if (libraryFolder.HasImage(ImageType.Primary)) + { + info.PrimaryImageItemId = libraryFolderId; + } + + info.LibraryOptions = GetLibraryOptions(libraryFolder); + + if (refreshQueue is not null) + { + info.RefreshProgress = libraryFolder.GetRefreshProgress(); + + info.RefreshStatus = info.RefreshProgress.HasValue ? "Active" : refreshQueue.Contains(libraryFolder.Id) ? "Queued" : "Idle"; + } + } + + return info; + } + + private CollectionTypeOptions? GetCollectionType(string path) + { + var files = _fileSystem.GetFilePaths(path, [".collection"], true, false); + foreach (ReadOnlySpan<char> file in files) + { + if (Enum.TryParse<CollectionTypeOptions>(Path.GetFileNameWithoutExtension(file), true, out var res)) + { + return res; + } + } + + return null; + } + + /// <inheritdoc /> + public BaseItem? GetItemById(Guid id) + { + if (id.IsEmpty()) + { + throw new ArgumentException("Guid can't be empty", nameof(id)); + } + + if (_cache.TryGet(id, out var item)) + { + return item; + } + + item = RetrieveItem(id); + + if (item is not null) + { + RegisterItem(item); + } + + return item; + } + + /// <inheritdoc /> + public T? GetItemById<T>(Guid id) + where T : BaseItem + { + var item = GetItemById(id); + if (item is T typedItem) + { + return typedItem; + } + + return null; + } + + /// <inheritdoc /> + public T? GetItemById<T>(Guid id, Guid userId) + where T : BaseItem + { + var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId); + return GetItemById<T>(id, user); + } + + /// <inheritdoc /> + public T? GetItemById<T>(Guid id, User? user) + where T : BaseItem + { + var item = GetItemById<T>(id); + return ItemIsVisible(item, user) ? item : null; + } + + public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent) + { + if (query.Recursive && !query.ParentId.IsEmpty()) + { + var parent = GetItemById(query.ParentId); + if (parent is not null) + { + SetTopParentIdsOrAncestors(query, [parent]); + } + } + + if (query.User is not null) + { + AddUserToQuery(query, query.User, allowExternalContent); + } + + var itemList = _itemRepository.GetItemList(query); + var user = query.User; + if (user is not null) + { + return itemList.Where(i => i.IsVisible(user)).ToList(); + } + + return itemList; + } + + public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query) + { + return GetItemList(query, true); + } + + public int GetCount(InternalItemsQuery query) + { + if (query.Recursive && !query.ParentId.IsEmpty()) + { + var parent = GetItemById(query.ParentId); + if (parent is not null) + { + SetTopParentIdsOrAncestors(query, [parent]); + } + } + + if (query.User is not null) + { + AddUserToQuery(query, query.User); + } + + return _itemRepository.GetCount(query); + } + + public ItemCounts GetItemCounts(InternalItemsQuery query) + { + if (query.Recursive && !query.ParentId.IsEmpty()) + { + var parent = GetItemById(query.ParentId); + if (parent is not null) + { + SetTopParentIdsOrAncestors(query, [parent]); + } + } + + if (query.User is not null) + { + AddUserToQuery(query, query.User); + } + + return _itemRepository.GetItemCounts(query); + } + + public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents) + { + SetTopParentIdsOrAncestors(query, parents); + + if (query.AncestorIds.Length == 0 && query.TopParentIds.Length == 0) + { + if (query.User is not null) + { + AddUserToQuery(query, query.User); + } + } + + return _itemRepository.GetItemList(query); + } + + public IReadOnlyList<BaseItem> GetLatestItemList(InternalItemsQuery query, IReadOnlyList<BaseItem> parents, CollectionType collectionType) + { + SetTopParentIdsOrAncestors(query, parents); + + if (query.AncestorIds.Length == 0 && query.TopParentIds.Length == 0) + { + if (query.User is not null) + { + AddUserToQuery(query, query.User); + } + } + + return _itemRepository.GetLatestItemList(query, collectionType); + } + + public IReadOnlyList<string> GetNextUpSeriesKeys(InternalItemsQuery query, IReadOnlyCollection<BaseItem> parents, DateTime dateCutoff) + { + SetTopParentIdsOrAncestors(query, parents); + + if (query.AncestorIds.Length == 0 && query.TopParentIds.Length == 0) + { + if (query.User is not null) + { + AddUserToQuery(query, query.User); + } + } + + return _itemRepository.GetNextUpSeriesKeys(query, dateCutoff); + } + + public QueryResult<BaseItem> QueryItems(InternalItemsQuery query) + { + if (query.User is not null) + { + AddUserToQuery(query, query.User); + } + + if (query.EnableTotalRecordCount) + { + return _itemRepository.GetItems(query); + } + + return new QueryResult<BaseItem>( + query.StartIndex, + null, + _itemRepository.GetItemList(query)); + } + + public IReadOnlyList<Guid> GetItemIds(InternalItemsQuery query) + { + if (query.User is not null) + { + AddUserToQuery(query, query.User); + } + + return _itemRepository.GetItemIdsList(query); + } + + public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query) + { + if (query.User is not null) + { + AddUserToQuery(query, query.User); + } + + SetTopParentOrAncestorIds(query); + return _itemRepository.GetStudios(query); + } + + public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query) + { + if (query.User is not null) + { + AddUserToQuery(query, query.User); + } + + SetTopParentOrAncestorIds(query); + return _itemRepository.GetGenres(query); + } + + public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query) + { + if (query.User is not null) + { + AddUserToQuery(query, query.User); + } + + SetTopParentOrAncestorIds(query); + return _itemRepository.GetMusicGenres(query); + } + + public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query) + { + if (query.User is not null) + { + AddUserToQuery(query, query.User); + } + + SetTopParentOrAncestorIds(query); + return _itemRepository.GetAllArtists(query); + } + + public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query) + { + if (query.User is not null) + { + AddUserToQuery(query, query.User); + } + + SetTopParentOrAncestorIds(query); + return _itemRepository.GetArtists(query); + } + + private void SetTopParentOrAncestorIds(InternalItemsQuery query) + { + var ancestorIds = query.AncestorIds; + int len = ancestorIds.Length; + if (len == 0) + { + return; + } + + var parents = new BaseItem[len]; + for (int i = 0; i < len; i++) + { + parents[i] = GetItemById(ancestorIds[i]) ?? throw new ArgumentException($"Failed to find parent with id: {ancestorIds[i]}"); + if (parents[i] is not (ICollectionFolder or UserView)) + { + return; + } + } + + // Optimize by querying against top level views + query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray(); + query.AncestorIds = []; + + // Prevent searching in all libraries due to empty filter + if (query.TopParentIds.Length == 0) + { + query.TopParentIds = [Guid.NewGuid()]; + } + } + + public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query) + { + if (query.User is not null) + { + AddUserToQuery(query, query.User); + } + + SetTopParentOrAncestorIds(query); + return _itemRepository.GetAlbumArtists(query); + } + + public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query) + { + if (query.Recursive && !query.ParentId.IsEmpty()) + { + var parent = GetItemById(query.ParentId); + if (parent is not null) + { + SetTopParentIdsOrAncestors(query, [parent]); + } + } + + if (query.User is not null) + { + AddUserToQuery(query, query.User); + } + + if (query.EnableTotalRecordCount) + { + return _itemRepository.GetItems(query); + } + + return new QueryResult<BaseItem>( + query.StartIndex, + null, + _itemRepository.GetItemList(query)); + } + + private void SetTopParentIdsOrAncestors(InternalItemsQuery query, IReadOnlyCollection<BaseItem> parents) + { + if (parents.All(i => i is ICollectionFolder || i is UserView)) + { + // Optimize by querying against top level views + query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray(); + + // Prevent searching in all libraries due to empty filter + if (query.TopParentIds.Length == 0) + { + query.TopParentIds = [Guid.NewGuid()]; + } + } + else + { + // We need to be able to query from any arbitrary ancestor up the tree + query.AncestorIds = parents.SelectMany(i => i.GetIdsForAncestorQuery()).ToArray(); + + // Prevent searching in all libraries due to empty filter + if (query.AncestorIds.Length == 0) + { + query.AncestorIds = [Guid.NewGuid()]; + } + } + + query.Parent = null; + } + + private void AddUserToQuery(InternalItemsQuery query, User user, bool allowExternalContent = true) + { + if (query.AncestorIds.Length == 0 && + query.ParentId.IsEmpty() && + query.ChannelIds.Count == 0 && + query.TopParentIds.Length == 0 && + string.IsNullOrEmpty(query.AncestorWithPresentationUniqueKey) && + string.IsNullOrEmpty(query.SeriesPresentationUniqueKey) && + query.ItemIds.Length == 0) + { + var userViews = UserViewManager.GetUserViews(new UserViewQuery + { + User = user, + IncludeHidden = true, + IncludeExternalContent = allowExternalContent + }); + + query.TopParentIds = userViews.SelectMany(i => GetTopParentIdsForQuery(i, user)).ToArray(); + + // Prevent searching in all libraries due to empty filter + if (query.TopParentIds.Length == 0) + { + query.TopParentIds = [Guid.NewGuid()]; + } + } + } + + private IEnumerable<Guid> GetTopParentIdsForQuery(BaseItem item, User? user) + { + if (item is UserView view) + { + if (view.ViewType == CollectionType.livetv) + { + return [view.Id]; + } + + // Translate view into folders + if (!view.DisplayParentId.IsEmpty()) + { + var displayParent = GetItemById(view.DisplayParentId); + if (displayParent is not null) + { + return GetTopParentIdsForQuery(displayParent, user); + } + + return []; + } + + if (!view.ParentId.IsEmpty()) + { + var displayParent = GetItemById(view.ParentId); + if (displayParent is not null) + { + return GetTopParentIdsForQuery(displayParent, user); + } + + return []; + } + + // Handle grouping + if (user is not null && view.ViewType != CollectionType.unknown && UserView.IsEligibleForGrouping(view.ViewType) + && user.GetPreference(PreferenceKind.GroupedFolders).Length > 0) + { + return GetUserRootFolder() + .GetChildren(user, true) + .OfType<CollectionFolder>() + .Where(i => i.CollectionType is null || i.CollectionType == view.ViewType) + .Where(i => user.IsFolderGrouped(i.Id)) + .SelectMany(i => GetTopParentIdsForQuery(i, user)); + } + + return []; + } + + if (item is CollectionFolder collectionFolder) + { + return collectionFolder.PhysicalFolderIds; + } + + var topParent = item.GetTopParent(); + if (topParent is not null) + { + return [topParent.Id]; + } + + return []; + } + + /// <summary> + /// Gets the intros. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="user">The user.</param> + /// <returns>IEnumerable{System.String}.</returns> + public async Task<IEnumerable<Video>> GetIntros(BaseItem item, User user) + { + if (IntroProviders.Length == 0) + { + return []; + } + + var tasks = IntroProviders + .Select(i => GetIntros(i, item, user)); + + var items = await Task.WhenAll(tasks).ConfigureAwait(false); + + return items + .SelectMany(i => i) + .Select(ResolveIntro) + .Where(i => i is not null)!; // null values got filtered out + } + + /// <summary> + /// Gets the intros. + /// </summary> + /// <param name="provider">The provider.</param> + /// <param name="item">The item.</param> + /// <param name="user">The user.</param> + /// <returns>Task<IEnumerable<IntroInfo>>.</returns> + private async Task<IEnumerable<IntroInfo>> GetIntros(IIntroProvider provider, BaseItem item, User user) + { + try + { + return await provider.GetIntros(item, user).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting intros"); + + return []; + } + } + + /// <summary> + /// Resolves the intro. + /// </summary> + /// <param name="info">The info.</param> + /// <returns>Video.</returns> + private Video? ResolveIntro(IntroInfo info) + { + Video? video = null; + + if (info.ItemId.HasValue) + { + // Get an existing item by Id + video = GetItemById(info.ItemId.Value) as Video; + + if (video is null) + { + _logger.LogError("Unable to locate item with Id {ID}.", info.ItemId.Value); + } + } + else if (!string.IsNullOrEmpty(info.Path)) + { + try + { + // Try to resolve the path into a video + video = ResolvePath(_fileSystem.GetFileSystemInfo(info.Path)) as Video; + + if (video is null) + { + _logger.LogError("Intro resolver returned null for {Path}.", info.Path); + } + else + { + // Pull the saved db item that will include metadata + var dbItem = GetItemById(video.Id) as Video; + + if (dbItem is not null) + { + video = dbItem; + } + else + { + return null; + } + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error resolving path {Path}.", info.Path); + } + } + else + { + _logger.LogError("IntroProvider returned an IntroInfo with null Path and ItemId."); + } + + return video; + } + + /// <inheritdoc /> + public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User? user, IEnumerable<ItemSortBy> sortBy, SortOrder sortOrder) + { + IOrderedEnumerable<BaseItem>? orderedItems = null; + + foreach (var orderBy in sortBy.Select(o => GetComparer(o, user)).Where(c => c is not null)) + { + if (orderBy is RandomComparer) + { + var randomItems = items.ToArray(); + Random.Shared.Shuffle(randomItems); + items = randomItems; + // Items are no longer ordered at this point, so set orderedItems back to null + orderedItems = null; + } + else if (orderedItems is null) + { + orderedItems = sortOrder == SortOrder.Descending + ? items.OrderByDescending(i => i, orderBy) + : items.OrderBy(i => i, orderBy); + } + else + { + orderedItems = sortOrder == SortOrder.Descending + ? orderedItems!.ThenByDescending(i => i, orderBy) + : orderedItems!.ThenBy(i => i, orderBy); // orderedItems is set during the first iteration + } + } + + return orderedItems ?? items; + } + + /// <inheritdoc /> + public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User? user, IEnumerable<(ItemSortBy OrderBy, SortOrder SortOrder)> orderBy) + { + IOrderedEnumerable<BaseItem>? orderedItems = null; + + foreach (var (name, sortOrder) in orderBy) + { + var comparer = GetComparer(name, user); + if (comparer is null) + { + continue; + } + + if (comparer is RandomComparer) + { + var randomItems = items.ToArray(); + Random.Shared.Shuffle(randomItems); + items = randomItems; + // Items are no longer ordered at this point, so set orderedItems back to null + orderedItems = null; + } + else if (orderedItems is null) + { + orderedItems = sortOrder == SortOrder.Descending + ? items.OrderByDescending(i => i, comparer) + : items.OrderBy(i => i, comparer); + } + else + { + orderedItems = sortOrder == SortOrder.Descending + ? orderedItems!.ThenByDescending(i => i, comparer) + : orderedItems!.ThenBy(i => i, comparer); // orderedItems is set during the first iteration + } + } + + return orderedItems ?? items; + } + + /// <summary> + /// Gets the comparer. + /// </summary> + /// <param name="name">The name.</param> + /// <param name="user">The user.</param> + /// <returns>IBaseItemComparer.</returns> + private IBaseItemComparer? GetComparer(ItemSortBy name, User? user) + { + var comparer = Comparers.FirstOrDefault(c => name == c.Type); + + // If it requires a user, create a new one, and assign the user + if (comparer is IUserBaseItemComparer) + { + var userComparer = (IUserBaseItemComparer)Activator.CreateInstance(comparer.GetType())!; // only null for Nullable<T> instances + + userComparer.User = user; + userComparer.UserManager = _userManager; + userComparer.UserDataManager = _userDataManager; + + return userComparer; + } + + return comparer; + } + + /// <inheritdoc /> + public void CreateItem(BaseItem item, BaseItem? parent) + { + CreateItems([item], parent, CancellationToken.None); + } + + /// <inheritdoc /> + public void CreateItems(IReadOnlyList<BaseItem> items, BaseItem? parent, CancellationToken cancellationToken) + { + _itemRepository.SaveItems(items, cancellationToken); + + foreach (var item in items) + { + RegisterItem(item); + } + + if (parent is Folder folder) + { + folder.Children = null; + folder.UserData = null; + } + + if (ItemAdded is not null) + { + foreach (var item in items) + { + // With the live tv guide this just creates too much noise + if (item.SourceType != SourceType.Library) + { + continue; + } + + try + { + ItemAdded( + this, + new ItemChangeEventArgs + { + Item = item, + Parent = parent ?? item.GetParent() + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in ItemAdded event handler"); + } + } + } + } + + private bool ImageNeedsRefresh(ItemImageInfo image) + { + if (image.Path is not null && image.IsLocalFile) + { + if (image.Width == 0 || image.Height == 0 || string.IsNullOrEmpty(image.BlurHash)) + { + return true; + } + + try + { + return image.DateModified.Subtract(_fileSystem.GetLastWriteTimeUtc(image.Path)).Duration().TotalSeconds > 1; + } + catch (Exception ex) + { + _logger.LogError(ex, "Cannot get file info for {0}", image.Path); + return false; + } + } + + return image.Path is not null && !image.IsLocalFile; + } + + /// <inheritdoc /> + public async Task UpdateImagesAsync(BaseItem item, bool forceUpdate = false) + { + ArgumentNullException.ThrowIfNull(item); + + var outdated = forceUpdate + ? item.ImageInfos.Where(i => i.Path is not null).ToArray() + : item.ImageInfos.Where(ImageNeedsRefresh).ToArray(); + // Skip image processing if current or live tv source + if (outdated.Length == 0 || item.SourceType != SourceType.Library) + { + RegisterItem(item); + return; + } + + foreach (var img in outdated) + { + var image = img; + if (!img.IsLocalFile) + { + try + { + var index = item.GetImageIndex(img); + image = await ConvertImageToLocal(item, img, index, true).ConfigureAwait(false); + } + catch (ArgumentException) + { + _logger.LogWarning("Cannot get image index for {ImagePath}", img.Path); + continue; + } + catch (Exception ex) when (ex is InvalidOperationException or IOException) + { + _logger.LogWarning(ex, "Cannot fetch image from {ImagePath}", img.Path); + continue; + } + catch (HttpRequestException ex) + { + _logger.LogWarning(ex, "Cannot fetch image from {ImagePath}. Http status code: {HttpStatus}", img.Path, ex.StatusCode); + continue; + } + } + + if (!File.Exists(image.Path)) + { + _logger.LogWarning("Image not found at {ImagePath}", image.Path); + continue; + } + + ImageDimensions size; + try + { + size = _imageProcessor.GetImageDimensions(item, image); + image.Width = size.Width; + image.Height = size.Height; + } + catch (Exception ex) + { + _logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path); + size = default; + image.Width = 0; + image.Height = 0; + } + + try + { + var blurhash = _imageProcessor.GetImageBlurHash(image.Path, size); + image.BlurHash = blurhash; + } + catch (Exception ex) + { + _logger.LogError(ex, "Cannot compute blurhash for {ImagePath}", image.Path); + image.BlurHash = string.Empty; + } + + try + { + var modifiedDate = _fileSystem.GetLastWriteTimeUtc(image.Path); + image.DateModified = modifiedDate; + } + catch (Exception ex) + { + _logger.LogError(ex, "Cannot update DateModified for {ImagePath}", image.Path); + } + } + + item.ValidateImages(); + + await _itemRepository.SaveImagesAsync(item).ConfigureAwait(false); + + RegisterItem(item); + } + + /// <inheritdoc /> + public async Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) + { + foreach (var item in items) + { + item.DateLastSaved = DateTime.UtcNow; + await RunMetadataSavers(item, updateReason).ConfigureAwait(false); + + // Modify again, so saved value is after write time of externally saved metadata + item.DateLastSaved = DateTime.UtcNow; + } + + _itemRepository.SaveItems(items, cancellationToken); + + if (parent is Folder folder) + { + folder.Children = null; + folder.UserData = null; + } + + if (ItemUpdated is not null) + { + foreach (var item in items) + { + // With the live tv guide this just creates too much noise + if (item.SourceType != SourceType.Library) + { + continue; + } + + try + { + ItemUpdated( + this, + new ItemChangeEventArgs + { + Item = item, + Parent = parent, + UpdateReason = updateReason + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in ItemUpdated event handler"); + } + } + } + } + + /// <inheritdoc /> + public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) + => UpdateItemsAsync([item], parent, updateReason, cancellationToken); + + /// <inheritdoc /> + public async Task ReattachUserDataAsync(BaseItem item, CancellationToken cancellationToken) + { + await _itemRepository.ReattachUserDataAsync(item, cancellationToken).ConfigureAwait(false); + } + + public async Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason) + { + if (item.IsFileProtocol) + { + await ProviderManager.SaveMetadataAsync(item, updateReason).ConfigureAwait(false); + } + + await UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false); + } + + /// <summary> + /// Reports the item removed. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="parent">The parent item.</param> + public void ReportItemRemoved(BaseItem item, BaseItem parent) + { + if (ItemRemoved is not null) + { + try + { + ItemRemoved( + this, + new ItemChangeEventArgs + { + Item = item, + Parent = parent + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in ItemRemoved event handler"); + } + } + } + + /// <summary> + /// Retrieves the item. + /// </summary> + /// <param name="id">The id.</param> + /// <returns>BaseItem.</returns> + public BaseItem RetrieveItem(Guid id) + { + return _itemRepository.RetrieveItem(id); + } + + public List<Folder> GetCollectionFolders(BaseItem item) + { + return GetCollectionFolders(item, GetUserRootFolder().Children.OfType<Folder>()); + } + + public List<Folder> GetCollectionFolders(BaseItem item, IEnumerable<Folder> allUserRootChildren) + { + while (item is not null) + { + var parent = item.GetParent(); + + if (parent is AggregateFolder) + { + break; + } + + if (parent is null) + { + var owner = item.GetOwner(); + + if (owner is null) + { + break; + } + + item = owner; + } + else + { + item = parent; + } + } + + if (item is null) + { + return new List<Folder>(); + } + + return GetCollectionFoldersInternal(item, allUserRootChildren); + } + + private static List<Folder> GetCollectionFoldersInternal(BaseItem item, IEnumerable<Folder> allUserRootChildren) + { + return allUserRootChildren + .Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path.AsSpan(), StringComparison.OrdinalIgnoreCase)) + .ToList(); + } + + public LibraryOptions GetLibraryOptions(BaseItem item) + { + if (item is CollectionFolder collectionFolder) + { + return collectionFolder.GetLibraryOptions(); + } + + // List.Find is more performant than FirstOrDefault due to enumerator allocation + return GetCollectionFolders(item) + .Find(folder => folder is CollectionFolder) is CollectionFolder collectionFolder2 + ? collectionFolder2.GetLibraryOptions() + : new LibraryOptions(); + } + + public CollectionType? GetContentType(BaseItem item) + { + var configuredContentType = GetConfiguredContentType(item, false); + if (configuredContentType is not null) + { + return configuredContentType; + } + + configuredContentType = GetConfiguredContentType(item, true); + if (configuredContentType is not null) + { + return configuredContentType; + } + + return GetInheritedContentType(item); + } + + public CollectionType? GetInheritedContentType(BaseItem item) + { + var type = GetTopFolderContentType(item); + + if (type is not null) + { + return type; + } + + return item.GetParents() + .Select(GetConfiguredContentType) + .LastOrDefault(i => i is not null); + } + + public CollectionType? GetConfiguredContentType(BaseItem item) + { + return GetConfiguredContentType(item, false); + } + + public CollectionType? GetConfiguredContentType(string path) + { + return GetContentTypeOverride(path, false); + } + + public CollectionType? GetConfiguredContentType(BaseItem item, bool inheritConfiguredPath) + { + if (item is ICollectionFolder collectionFolder) + { + return collectionFolder.CollectionType; + } + + return GetContentTypeOverride(item.ContainingFolderPath, inheritConfiguredPath); + } + + private CollectionType? GetContentTypeOverride(string path, bool inherit) + { + var nameValuePair = _configurationManager.Configuration.ContentTypes + .FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path) + || (inherit && !string.IsNullOrEmpty(i.Name) + && _fileSystem.ContainsSubPath(i.Name, path))); + if (Enum.TryParse<CollectionType>(nameValuePair?.Value, out var collectionType)) + { + return collectionType; + } + + return null; + } + + private CollectionType? GetTopFolderContentType(BaseItem item) + { + if (item is null) + { + return null; + } + + while (!item.ParentId.IsEmpty()) + { + var parent = item.GetParent(); + if (parent is null || parent is AggregateFolder) + { + break; + } + + item = parent; + } + + return GetUserRootFolder().Children + .OfType<ICollectionFolder>() + .Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path)) + .Select(i => i.CollectionType) + .FirstOrDefault(i => i is not null); + } + + public UserView GetNamedView( + User user, + string name, + CollectionType? viewType, + string sortName) + { + return GetNamedView(user, name, Guid.Empty, viewType, sortName); + } + + public UserView GetNamedView( + string name, + CollectionType viewType, + string sortName) + { + var path = Path.Combine( + _configurationManager.ApplicationPaths.InternalMetadataPath, + "views", + _fileSystem.GetValidFilename(viewType.ToString())); + + var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView)); + + var item = GetItemById(id) as UserView; + + var refresh = false; + + if (item is null || !string.Equals(item.Path, path, StringComparison.OrdinalIgnoreCase)) + { + var info = Directory.CreateDirectory(path); + item = new UserView + { + Path = path, + Id = id, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, + Name = name, + ViewType = viewType, + ForcedSortName = sortName + }; + + CreateItem(item, null); + + refresh = true; + } + + if (refresh) + { + item.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, CancellationToken.None).GetAwaiter().GetResult(); + ProviderManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal); + } + + return item; + } + + public UserView GetNamedView( + User user, + string name, + Guid parentId, + CollectionType? viewType, + string sortName) + { + var parentIdString = parentId.IsEmpty() + ? null + : parentId.ToString("N", CultureInfo.InvariantCulture); + var idValues = "38_namedview_" + name + user.Id.ToString("N", CultureInfo.InvariantCulture) + (parentIdString ?? string.Empty) + (viewType?.ToString() ?? string.Empty); + + var id = GetNewItemId(idValues, typeof(UserView)); + + var path = Path.Combine(_configurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture)); + + var item = GetItemById(id) as UserView; + + var isNew = false; + + if (item is null) + { + var info = Directory.CreateDirectory(path); + item = new UserView + { + Path = path, + Id = id, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, + Name = name, + ViewType = viewType, + ForcedSortName = sortName, + UserId = user.Id, + DisplayParentId = parentId + }; + + CreateItem(item, null); + + isNew = true; + } + + var lastRefreshedUtc = item.DateLastRefreshed; + var refresh = isNew || DateTime.UtcNow - lastRefreshedUtc >= _viewRefreshInterval; + + if (!refresh && !item.DisplayParentId.IsEmpty()) + { + var displayParent = GetItemById(item.DisplayParentId); + refresh = displayParent is not null && displayParent.DateLastSaved > lastRefreshedUtc; + } + + if (refresh) + { + ProviderManager.QueueRefresh( + item.Id, + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + // Need to force save to increment DateLastSaved + ForceSave = true + }, + RefreshPriority.Normal); + } + + return item; + } + + public UserView GetShadowView( + BaseItem parent, + CollectionType? viewType, + string sortName) + { + ArgumentNullException.ThrowIfNull(parent); + + var name = parent.Name; + var parentId = parent.Id; + + var idValues = "38_namedview_" + name + parentId + (viewType?.ToString() ?? string.Empty); + + var id = GetNewItemId(idValues, typeof(UserView)); + + var path = parent.Path; + + var item = GetItemById(id) as UserView; + + var isNew = false; + + if (item is null) + { + var info = Directory.CreateDirectory(path); + item = new UserView + { + Path = path, + Id = id, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, + Name = name, + ViewType = viewType, + ForcedSortName = sortName, + DisplayParentId = parentId + }; + + CreateItem(item, null); + + isNew = true; + } + + var lastRefreshedUtc = item.DateLastRefreshed; + var refresh = isNew || DateTime.UtcNow - lastRefreshedUtc >= _viewRefreshInterval; + + if (!refresh && !item.DisplayParentId.IsEmpty()) + { + var displayParent = GetItemById(item.DisplayParentId); + refresh = displayParent is not null && displayParent.DateLastSaved > lastRefreshedUtc; + } + + if (refresh) + { + ProviderManager.QueueRefresh( + item.Id, + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + // Need to force save to increment DateLastSaved + ForceSave = true + }, + RefreshPriority.Normal); + } + + return item; + } + + public UserView GetNamedView( + string name, + Guid parentId, + CollectionType? viewType, + string sortName, + string uniqueId) + { + ArgumentException.ThrowIfNullOrEmpty(name); + + var parentIdString = parentId.IsEmpty() + ? null + : parentId.ToString("N", CultureInfo.InvariantCulture); + var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType?.ToString() ?? string.Empty); + if (!string.IsNullOrEmpty(uniqueId)) + { + idValues += uniqueId; + } + + var id = GetNewItemId(idValues, typeof(UserView)); + + var path = Path.Combine(_configurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture)); + + var item = GetItemById(id) as UserView; + + var isNew = false; + + if (item is null) + { + var info = Directory.CreateDirectory(path); + item = new UserView + { + Path = path, + Id = id, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, + Name = name, + ViewType = viewType, + ForcedSortName = sortName, + DisplayParentId = parentId + }; + + CreateItem(item, null); + + isNew = true; + } + + if (viewType != item.ViewType) + { + item.ViewType = viewType; + item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult(); + } + + var lastRefreshedUtc = item.DateLastRefreshed; + var refresh = isNew || DateTime.UtcNow - lastRefreshedUtc >= _viewRefreshInterval; + + if (!refresh && !item.DisplayParentId.IsEmpty()) + { + var displayParent = GetItemById(item.DisplayParentId); + refresh = displayParent is not null && displayParent.DateLastSaved > lastRefreshedUtc; + } + + if (refresh) + { + ProviderManager.QueueRefresh( + item.Id, + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + // Need to force save to increment DateLastSaved + ForceSave = true + }, + RefreshPriority.Normal); + } + + return item; + } + + public BaseItem GetParentItem(Guid? parentId, Guid? userId) + { + if (parentId.HasValue) + { + return GetItemById(parentId.Value) ?? throw new ArgumentException($"Invalid parent id: {parentId.Value}"); + } + + if (!userId.IsNullOrEmpty()) + { + return GetUserRootFolder(); + } + + return RootFolder; + } + + /// <inheritdoc /> + public void QueueLibraryScan() + { + _taskManager.QueueScheduledTask<RefreshMediaLibraryTask>(); + } + + /// <inheritdoc /> + public int? GetSeasonNumberFromPath(string path, Guid? parentId) + { + var parentPath = parentId.HasValue ? GetItemById(parentId.Value)?.ContainingFolderPath : null; + return SeasonPathParser.Parse(path, parentPath, true, true).SeasonNumber; + } + + /// <inheritdoc /> + public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh) + { + var series = episode.Series; + bool? isAbsoluteNaming = series is not null && string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase); + if (!isAbsoluteNaming.Value) + { + // In other words, no filter applied + isAbsoluteNaming = null; + } + + var resolver = new EpisodeResolver(_namingOptions); + + var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd; + + EpisodeInfo? episodeInfo = null; + if (episode.IsFileProtocol) + { + episodeInfo = resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming); + // Resolve from parent folder if it's not the Season folder + var parent = episode.GetParent(); + if (episodeInfo is null && parent.GetType() == typeof(Folder)) + { + episodeInfo = resolver.Resolve(parent.Path, true, null, null, isAbsoluteNaming); + if (episodeInfo is not null) + { + // add the container + episodeInfo.Container = Path.GetExtension(episode.Path)?.TrimStart('.'); + } + } + } + + var changed = false; + if (episodeInfo is null) + { + return changed; + } + + if (episodeInfo.IsByDate) + { + if (episode.IndexNumber.HasValue) + { + episode.IndexNumber = null; + changed = true; + } + + if (episode.IndexNumberEnd.HasValue) + { + episode.IndexNumberEnd = null; + changed = true; + } + + if (!episode.PremiereDate.HasValue) + { + if (episodeInfo.Year.HasValue && episodeInfo.Month.HasValue && episodeInfo.Day.HasValue) + { + episode.PremiereDate = new DateTime(episodeInfo.Year.Value, episodeInfo.Month.Value, episodeInfo.Day.Value).ToUniversalTime(); + } + + if (episode.PremiereDate.HasValue) + { + changed = true; + } + } + + if (!episode.ProductionYear.HasValue) + { + episode.ProductionYear = episodeInfo.Year; + + if (episode.ProductionYear.HasValue) + { + changed = true; + } + } + } + else + { + if (!episode.IndexNumber.HasValue || forceRefresh) + { + if (episode.IndexNumber != episodeInfo.EpisodeNumber) + { + changed = true; + } + + episode.IndexNumber = episodeInfo.EpisodeNumber; + } + + if (!episode.IndexNumberEnd.HasValue || forceRefresh) + { + if (episode.IndexNumberEnd != episodeInfo.EndingEpisodeNumber) + { + changed = true; + } + + episode.IndexNumberEnd = episodeInfo.EndingEpisodeNumber; + } + + if (!episode.ParentIndexNumber.HasValue || forceRefresh) + { + if (episode.ParentIndexNumber != episodeInfo.SeasonNumber) + { + changed = true; + } + + episode.ParentIndexNumber = episodeInfo.SeasonNumber; + } + } + + if (!episode.ParentIndexNumber.HasValue) + { + var season = episode.Season; + + if (season is not null) + { + episode.ParentIndexNumber = season.IndexNumber; + } + + if (episode.ParentIndexNumber.HasValue) + { + changed = true; + } + } + + return changed; + } + + public ItemLookupInfo ParseName(string name) + { + var namingOptions = _namingOptions; + var result = VideoResolver.CleanDateTime(name, namingOptions); + + return new ItemLookupInfo + { + Name = VideoResolver.TryCleanString(result.Name, namingOptions, out var newName) ? newName : result.Name, + Year = result.Year + }; + } + + public IEnumerable<BaseItem> FindExtras(BaseItem owner, IReadOnlyList<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) + { + // Apply .ignore rules + var filtered = fileSystemChildren.Where(c => !DotIgnoreIgnoreRule.IsIgnored(c, owner)).ToList(); + var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions, libraryRoot: owner.ContainingFolderPath); + if (ownerVideoInfo is null) + { + yield break; + } + + var count = filtered.Count; + for (var i = 0; i < count; i++) + { + var current = filtered[i]; + if (current.IsDirectory && _namingOptions.AllExtrasTypesFolderNames.ContainsKey(current.Name)) + { + var filesInSubFolder = _fileSystem.GetFiles(current.FullName, null, false, false); + var filesInSubFolderList = filesInSubFolder.ToList(); + + bool subFolderIsMixedFolder = filesInSubFolderList.Count > 1; + + foreach (var file in filesInSubFolderList) + { + if (!_extraResolver.TryGetExtraTypeForOwner(file.FullName, ownerVideoInfo, out var extraType)) + { + continue; + } + + var extra = GetExtra(file, extraType.Value, subFolderIsMixedFolder); + if (extra is not null) + { + yield return extra; + } + } + } + else if (!current.IsDirectory && _extraResolver.TryGetExtraTypeForOwner(current.FullName, ownerVideoInfo, out var extraType)) + { + var extra = GetExtra(current, extraType.Value, false); + if (extra is not null) + { + yield return extra; + } + } + } + + BaseItem? GetExtra(FileSystemMetadata file, ExtraType extraType, bool isInMixedFolder) + { + var extra = ResolvePath(_fileSystem.GetFileInfo(file.FullName), directoryService, _extraResolver.GetResolversForExtraType(extraType)); + if (extra is not Video && extra is not Audio) + { + return null; + } + + // Try to retrieve it from the db. If we don't find it, use the resolved version + var itemById = GetItemById(extra.Id); + if (itemById is not null) + { + extra = itemById; + } + + // Only update extra type if it is more specific then the currently known extra type + if (extra.ExtraType is null or ExtraType.Unknown || extraType != ExtraType.Unknown) + { + extra.ExtraType = extraType; + } + + extra.ParentId = Guid.Empty; + extra.OwnerId = owner.Id; + extra.IsInMixedFolder = isInMixedFolder; + return extra; + } + } + + public string GetPathAfterNetworkSubstitution(string path, BaseItem? ownerItem) + { + foreach (var map in _configurationManager.Configuration.PathSubstitutions) + { + if (path.TryReplaceSubPath(map.From, map.To, out var newPath)) + { + return newPath; + } + } + + return path; + } + + public IReadOnlyList<PersonInfo> GetPeople(InternalPeopleQuery query) + { + return _peopleRepository.GetPeople(query); + } + + public IReadOnlyList<PersonInfo> GetPeople(BaseItem item) + { + if (item.SupportsPeople) + { + var people = GetPeople(new InternalPeopleQuery + { + ItemId = item.Id + }); + + if (people.Count > 0) + { + return people; + } + } + + return []; + } + + public IReadOnlyList<Person> GetPeopleItems(InternalPeopleQuery query) + { + return _peopleRepository.GetPeopleNames(query) + .Select(i => + { + try + { + return GetPerson(i); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting person"); + return null; + } + }) + .Where(i => i is not null) + .Where(i => query.User is null || i!.IsVisible(query.User)) + .ToList()!; // null values are filtered out + } + + public IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery query) + { + return _peopleRepository.GetPeopleNames(query); + } + + public void UpdatePeople(BaseItem item, List<PersonInfo> people) + { + UpdatePeopleAsync(item, people, CancellationToken.None).GetAwaiter().GetResult(); + } + + /// <inheritdoc /> + public async Task UpdatePeopleAsync(BaseItem item, IReadOnlyList<PersonInfo> people, CancellationToken cancellationToken) + { + if (!item.SupportsPeople) + { + return; + } + + if (people is not null) + { + people = people.Where(e => e is not null).ToArray(); + _peopleRepository.UpdatePeople(item.Id, people); + await SavePeopleMetadataAsync(people, cancellationToken).ConfigureAwait(false); + } + } + + public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex, bool removeOnFailure) + { + foreach (var url in image.Path.Split('|')) + { + try + { + _logger.LogDebug("ConvertImageToLocal item {0} - image url: {1}", item.Id, url); + + await ProviderManager.SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false); + + await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false); + + return item.GetImageInfo(image.Type, imageIndex); + } + catch (HttpRequestException ex) + { + if (ex.StatusCode.HasValue + && (ex.StatusCode.Value == HttpStatusCode.NotFound || ex.StatusCode.Value == HttpStatusCode.Forbidden)) + { + _logger.LogDebug(ex, "Error downloading image {Url}", url); + continue; + } + + throw; + } + } + + if (removeOnFailure) + { + // Remove this image to prevent it from retrying over and over + item.RemoveImage(image); + await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false); + } + + throw new InvalidOperationException("Unable to convert any images to local"); + } + + public async Task AddVirtualFolder(string name, CollectionTypeOptions? collectionType, LibraryOptions options, bool refreshLibrary) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentNullException(nameof(name)); + } + + name = _fileSystem.GetValidFilename(name.Trim()); + + var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; + + var existingNameCount = 1; // first numbered name will be 2 + var virtualFolderPath = Path.Combine(rootFolderPath, name); + var originalName = name; + while (Directory.Exists(virtualFolderPath)) + { + existingNameCount++; + name = originalName + existingNameCount; + virtualFolderPath = Path.Combine(rootFolderPath, name); + } + + var mediaPathInfos = options.PathInfos; + if (mediaPathInfos is not null) + { + var invalidpath = mediaPathInfos.FirstOrDefault(i => !Directory.Exists(i.Path)); + if (invalidpath is not null) + { + throw new ArgumentException("The specified path does not exist: " + invalidpath.Path + "."); + } + } + + LibraryMonitor.Stop(); + + try + { + Directory.CreateDirectory(virtualFolderPath); + + if (collectionType is not null) + { + var path = Path.Combine(virtualFolderPath, collectionType.ToString()!.ToLowerInvariant() + ".collection"); // Can't be null with legal values? + + FileHelper.CreateEmpty(path); + } + + CollectionFolder.SaveLibraryOptions(virtualFolderPath, options); + + if (mediaPathInfos is not null) + { + foreach (var path in mediaPathInfos) + { + AddMediaPathInternal(name, path, false); + } + } + } + finally + { + await ValidateTopLibraryFolders(CancellationToken.None).ConfigureAwait(false); + + if (refreshLibrary) + { + StartScanInBackground(); + } + else + { + // Need to add a delay here or directory watchers may still pick up the changes + await Task.Delay(1000).ConfigureAwait(false); + LibraryMonitor.Start(); + } + } + } + + private async Task SavePeopleMetadataAsync(IEnumerable<PersonInfo> people, CancellationToken cancellationToken) + { + foreach (var person in people) + { + cancellationToken.ThrowIfCancellationRequested(); + + var itemUpdateType = ItemUpdateType.MetadataDownload; + var saveEntity = false; + var createEntity = false; + var personEntity = GetPerson(person.Name); + + if (personEntity is null) + { + try + { + var path = Person.GetPath(person.Name); + var info = Directory.CreateDirectory(path); + personEntity = new Person() + { + Name = person.Name, + Id = GetItemByNameId<Person>(path), + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, + Path = path + }; + + personEntity.PresentationUniqueKey = personEntity.CreatePresentationUniqueKey(); + saveEntity = true; + createEntity = true; + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to create person {Name}", person.Name); + continue; + } + } + + foreach (var id in person.ProviderIds) + { + if (!string.Equals(personEntity.GetProviderId(id.Key), id.Value, StringComparison.OrdinalIgnoreCase)) + { + personEntity.SetProviderId(id.Key, id.Value); + saveEntity = true; + } + } + + if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary)) + { + personEntity.SetImage( + new ItemImageInfo + { + Path = person.ImageUrl, + Type = ImageType.Primary + }, + 0); + + saveEntity = true; + itemUpdateType = ItemUpdateType.ImageUpdate; + } + + if (saveEntity) + { + if (createEntity) + { + CreateItems([personEntity], null, CancellationToken.None); + } + + await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false); + personEntity.DateLastSaved = DateTime.UtcNow; + + CreateItems([personEntity], null, CancellationToken.None); + } + } + } + + private void StartScanInBackground() + { + Task.Run(() => + { + // No need to start if scanning the library because it will handle it + ValidateMediaLibrary(new Progress<double>(), CancellationToken.None); + }); + } + + public void AddMediaPath(string virtualFolderName, MediaPathInfo mediaPath) + { + AddMediaPathInternal(virtualFolderName, mediaPath, true); + } + + private void AddMediaPathInternal(string virtualFolderName, MediaPathInfo pathInfo, bool saveLibraryOptions) + { + ArgumentNullException.ThrowIfNull(pathInfo); + + var path = pathInfo.Path; + + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentException(nameof(path)); + } + + if (!Directory.Exists(path)) + { + throw new FileNotFoundException("The path does not exist."); + } + + var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; + var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); + + CreateShortcut(virtualFolderPath, pathInfo); + + if (saveLibraryOptions) + { + var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath); + + libraryOptions.PathInfos = [.. libraryOptions.PathInfos, pathInfo]; + + SyncLibraryOptionsToLocations(virtualFolderPath, libraryOptions); + + CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions); + } + } + + public void UpdateMediaPath(string virtualFolderName, MediaPathInfo mediaPath) + { + ArgumentNullException.ThrowIfNull(mediaPath); + + var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; + var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); + + var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath); + + SyncLibraryOptionsToLocations(virtualFolderPath, libraryOptions); + + CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions); + } + + private void SyncLibraryOptionsToLocations(string virtualFolderPath, LibraryOptions options) + { + var topLibraryFolders = GetUserRootFolder().Children.ToList(); + var info = GetVirtualFolderInfo(virtualFolderPath, topLibraryFolders, null); + + if (info.Locations.Length > 0 && info.Locations.Length != options.PathInfos.Length) + { + var list = options.PathInfos.ToList(); + + foreach (var location in info.Locations) + { + if (!list.Any(i => string.Equals(i.Path, location, StringComparison.Ordinal))) + { + list.Add(new MediaPathInfo(location)); + } + } + + options.PathInfos = list.ToArray(); + } + } + + public async Task RemoveVirtualFolder(string name, bool refreshLibrary) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentNullException(nameof(name)); + } + + var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; + + var path = Path.Combine(rootFolderPath, name); + + if (!Directory.Exists(path)) + { + throw new FileNotFoundException("The media folder does not exist"); + } + + LibraryMonitor.Stop(); + + try + { + Directory.Delete(path, true); + } + finally + { + CollectionFolder.OnCollectionFolderChange(); + + if (refreshLibrary) + { + await ValidateTopLibraryFolders(CancellationToken.None, true).ConfigureAwait(false); + + StartScanInBackground(); + } + else + { + // Need to add a delay here or directory watchers may still pick up the changes + await Task.Delay(1000).ConfigureAwait(false); + LibraryMonitor.Start(); + } + } + } + + private void RemoveContentTypeOverrides(string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentNullException(nameof(path)); + } + + List<NameValuePair>? removeList = null; + + foreach (var contentType in _configurationManager.Configuration.ContentTypes) + { + if (string.IsNullOrWhiteSpace(contentType.Name) + || _fileSystem.AreEqual(path, contentType.Name) + || _fileSystem.ContainsSubPath(path, contentType.Name)) + { + (removeList ??= new()).Add(contentType); + } + } + + if (removeList is not null) + { + _configurationManager.Configuration.ContentTypes = _configurationManager.Configuration.ContentTypes + .Except(removeList) + .ToArray(); + + _configurationManager.SaveConfiguration(); + } + } + + public void RemoveMediaPath(string virtualFolderName, string mediaPath) + { + ArgumentException.ThrowIfNullOrEmpty(mediaPath); + + var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; + var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); + + if (!Directory.Exists(virtualFolderPath)) + { + throw new FileNotFoundException( + string.Format(CultureInfo.InvariantCulture, "The media collection {0} does not exist", virtualFolderName)); + } + + var shortcut = _fileSystem.GetFilePaths(virtualFolderPath, true) + .Where(i => Path.GetExtension(i.AsSpan()).Equals(ShortcutFileExtension, StringComparison.OrdinalIgnoreCase)) + .FirstOrDefault(f => _appHost.ExpandVirtualPath(_fileSystem.ResolveShortcut(f)).Equals(mediaPath, StringComparison.OrdinalIgnoreCase)); + + if (!string.IsNullOrEmpty(shortcut)) + { + _fileSystem.DeleteFile(shortcut); + } + + var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath); + + libraryOptions.PathInfos = libraryOptions + .PathInfos + .Where(i => !string.Equals(i.Path, mediaPath, StringComparison.Ordinal)) + .ToArray(); + + CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions); + } + + private static bool ItemIsVisible(BaseItem? item, User? user) + { + if (item is null) + { + return false; + } + + if (user is null) + { + return true; + } + + return item is UserRootFolder || item.IsVisibleStandalone(user); + } + + public void CreateShortcut(string virtualFolderPath, MediaPathInfo pathInfo) + { + var path = pathInfo.Path; + var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; + + var shortcutFilename = Path.GetFileNameWithoutExtension(path); + + var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension); + + while (File.Exists(lnk)) + { + shortcutFilename += "1"; + lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension); + } + + _fileSystem.CreateShortcut(lnk, _appHost.ReverseVirtualPath(path)); + RemoveContentTypeOverrides(path); + } + } +} diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs new file mode 100644 index 00000000..0ebfe3ae --- /dev/null +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -0,0 +1,191 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Extensions.Json; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.MediaInfo; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library +{ + public class LiveStreamHelper + { + private readonly IMediaEncoder _mediaEncoder; + private readonly ILogger _logger; + private readonly IApplicationPaths _appPaths; + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + + public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IApplicationPaths appPaths) + { + _mediaEncoder = mediaEncoder; + _logger = logger; + _appPaths = appPaths; + } + + public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, string? cacheKey, bool addProbeDelay, CancellationToken cancellationToken) + { + var originalRuntime = mediaSource.RunTimeTicks; + + var now = DateTime.UtcNow; + + MediaInfo? mediaInfo = null; + var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N", CultureInfo.InvariantCulture) + ".json"); + + if (cacheFilePath is not null) + { + try + { + FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath); + + await using (jsonStream.ConfigureAwait(false)) + { + mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + // _logger.LogDebug("Found cached media info"); + } + } + catch (IOException ex) + { + _logger.LogDebug(ex, "Could not open cached media info"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error opening cached media info"); + } + } + + if (mediaInfo is null) + { + if (addProbeDelay) + { + var delayMs = mediaSource.AnalyzeDurationMs ?? 0; + delayMs = Math.Max(3000, delayMs); + _logger.LogInformation("Waiting {0}ms before probing the live stream", delayMs); + await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false); + } + + mediaSource.AnalyzeDurationMs = 3000; + + mediaInfo = await _mediaEncoder.GetMediaInfo( + new MediaInfoRequest + { + MediaSource = mediaSource, + MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, + ExtractChapters = false + }, + cancellationToken).ConfigureAwait(false); + + if (cacheFilePath is not null) + { + Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath) ?? throw new InvalidOperationException("Path can't be a root directory.")); + FileStream createStream = AsyncFile.OpenWrite(cacheFilePath); + await using (createStream.ConfigureAwait(false)) + { + await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false); + } + + _logger.LogDebug("Saved media info to {0}", cacheFilePath); + } + } + + var mediaStreams = mediaInfo.MediaStreams; + + if (!string.IsNullOrEmpty(cacheKey)) + { + var newList = new List<MediaStream>(); + newList.AddRange(mediaStreams.Where(i => i.Type == MediaStreamType.Video).Take(1)); + newList.AddRange(mediaStreams.Where(i => i.Type == MediaStreamType.Audio).Take(1)); + + foreach (var stream in newList) + { + stream.Index = -1; + stream.Language = null; + } + + mediaStreams = newList; + } + + _logger.LogInformation("Live tv media info probe took {0} seconds", (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture)); + + mediaSource.Bitrate = mediaInfo.Bitrate; + mediaSource.Container = mediaInfo.Container; + mediaSource.Formats = mediaInfo.Formats; + mediaSource.MediaStreams = mediaStreams; + mediaSource.RunTimeTicks = mediaInfo.RunTimeTicks; + mediaSource.Size = mediaInfo.Size; + mediaSource.Timestamp = mediaInfo.Timestamp; + mediaSource.Video3DFormat = mediaInfo.Video3DFormat; + mediaSource.VideoType = mediaInfo.VideoType; + + mediaSource.DefaultSubtitleStreamIndex = null; + + // Null this out so that it will be treated like a live stream + if (!originalRuntime.HasValue) + { + mediaSource.RunTimeTicks = null; + } + + var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); + + if (audioStream is null || audioStream.Index == -1) + { + mediaSource.DefaultAudioStreamIndex = null; + } + else + { + mediaSource.DefaultAudioStreamIndex = audioStream.Index; + } + + var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); + if (videoStream is not null) + { + if (!videoStream.BitRate.HasValue) + { + var width = videoStream.Width ?? 1920; + + if (width >= 3000) + { + videoStream.BitRate = 30000000; + } + else if (width >= 1900) + { + videoStream.BitRate = 20000000; + } + else if (width >= 1200) + { + videoStream.BitRate = 8000000; + } + else if (width >= 700) + { + videoStream.BitRate = 2000000; + } + } + + // This is coming up false and preventing stream copy + videoStream.IsAVC = null; + } + + mediaSource.AnalyzeDurationMs = 3000; + + // Try to estimate this + mediaSource.InferTotalBitrate(true); + } + + public Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, bool addProbeDelay, CancellationToken cancellationToken) + { + return AddMediaInfoWithProbe(mediaSource, isAudio, null, addProbeDelay, cancellationToken); + } + } +} diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs new file mode 100644 index 00000000..c667fb06 --- /dev/null +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -0,0 +1,924 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using AsyncKeyedLock; +using Jellyfin.Data; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using Jellyfin.Extensions.Json; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.MediaInfo; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library +{ + public class MediaSourceManager : IMediaSourceManager, IDisposable + { + // Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message. + private const char LiveStreamIdDelimiter = '_'; + + private readonly IServerApplicationHost _appHost; + private readonly IItemRepository _itemRepo; + private readonly IUserManager _userManager; + private readonly ILibraryManager _libraryManager; + private readonly IFileSystem _fileSystem; + private readonly ILogger<MediaSourceManager> _logger; + private readonly IUserDataManager _userDataManager; + private readonly IMediaEncoder _mediaEncoder; + private readonly ILocalizationManager _localizationManager; + private readonly IApplicationPaths _appPaths; + private readonly IDirectoryService _directoryService; + private readonly IMediaStreamRepository _mediaStreamRepository; + private readonly IMediaAttachmentRepository _mediaAttachmentRepository; + private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase); + private readonly AsyncNonKeyedLocker _liveStreamLocker = new(1); + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + + private IMediaSourceProvider[] _providers; + + public MediaSourceManager( + IServerApplicationHost appHost, + IItemRepository itemRepo, + IApplicationPaths applicationPaths, + ILocalizationManager localizationManager, + IUserManager userManager, + ILibraryManager libraryManager, + ILogger<MediaSourceManager> logger, + IFileSystem fileSystem, + IUserDataManager userDataManager, + IMediaEncoder mediaEncoder, + IDirectoryService directoryService, + IMediaStreamRepository mediaStreamRepository, + IMediaAttachmentRepository mediaAttachmentRepository) + { + _appHost = appHost; + _itemRepo = itemRepo; + _userManager = userManager; + _libraryManager = libraryManager; + _logger = logger; + _fileSystem = fileSystem; + _userDataManager = userDataManager; + _mediaEncoder = mediaEncoder; + _localizationManager = localizationManager; + _appPaths = applicationPaths; + _directoryService = directoryService; + _mediaStreamRepository = mediaStreamRepository; + _mediaAttachmentRepository = mediaAttachmentRepository; + } + + public void AddParts(IEnumerable<IMediaSourceProvider> providers) + { + _providers = providers.ToArray(); + } + + public IReadOnlyList<MediaStream> GetMediaStreams(MediaStreamQuery query) + { + var list = _mediaStreamRepository.GetMediaStreams(query); + + foreach (var stream in list) + { + stream.SupportsExternalStream = StreamSupportsExternalStream(stream); + } + + return list; + } + + private static bool StreamSupportsExternalStream(MediaStream stream) + { + if (stream.IsExternal) + { + return true; + } + + if (stream.IsTextSubtitleStream) + { + return true; + } + + if (stream.IsPgsSubtitleStream) + { + return true; + } + + return false; + } + + public IReadOnlyList<MediaStream> GetMediaStreams(Guid itemId) + { + var list = GetMediaStreams(new MediaStreamQuery + { + ItemId = itemId + }); + + return GetMediaStreamsForItem(list); + } + + private IReadOnlyList<MediaStream> GetMediaStreamsForItem(IReadOnlyList<MediaStream> streams) + { + foreach (var stream in streams) + { + if (stream.Type == MediaStreamType.Subtitle) + { + stream.SupportsExternalStream = StreamSupportsExternalStream(stream); + } + } + + return streams; + } + + /// <inheritdoc /> + public IReadOnlyList<MediaAttachment> GetMediaAttachments(MediaAttachmentQuery query) + { + return _mediaAttachmentRepository.GetMediaAttachments(query); + } + + /// <inheritdoc /> + public IReadOnlyList<MediaAttachment> GetMediaAttachments(Guid itemId) + { + return GetMediaAttachments(new MediaAttachmentQuery + { + ItemId = itemId + }); + } + + public async Task<IReadOnlyList<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken) + { + var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user); + + // If file is strm or main media stream is missing, force a metadata refresh with remote probing + if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder + && (item.Path.EndsWith(".strm", StringComparison.OrdinalIgnoreCase) + || (item.MediaType == MediaType.Video && mediaSources[0].MediaStreams.All(i => i.Type != MediaStreamType.Video)) + || (item.MediaType == MediaType.Audio && mediaSources[0].MediaStreams.All(i => i.Type != MediaStreamType.Audio)))) + { + await item.RefreshMetadata( + new MetadataRefreshOptions(_directoryService) + { + EnableRemoteContentProbe = true, + MetadataRefreshMode = MetadataRefreshMode.FullRefresh + }, + cancellationToken).ConfigureAwait(false); + + mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user); + } + + var dynamicMediaSources = await GetDynamicMediaSources(item, cancellationToken).ConfigureAwait(false); + + var list = new List<MediaSourceInfo>(); + + list.AddRange(mediaSources); + + foreach (var source in dynamicMediaSources) + { + // Validate that this is actually possible + if (source.SupportsDirectStream) + { + source.SupportsDirectStream = SupportsDirectStream(source.Path, source.Protocol); + } + + if (user is not null) + { + SetDefaultAudioAndSubtitleStreamIndices(item, source, user); + + if (item.MediaType == MediaType.Audio) + { + source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding); + } + else if (item.MediaType == MediaType.Video) + { + source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding); + source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing); + } + } + + list.Add(source); + } + + return SortMediaSources(list).ToArray(); + } + + /// <inheritdoc />> + public MediaProtocol GetPathProtocol(string path) + { + if (string.IsNullOrEmpty(path)) + { + return MediaProtocol.File; + } + + if (path.StartsWith("Rtsp", StringComparison.OrdinalIgnoreCase)) + { + return MediaProtocol.Rtsp; + } + + if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase)) + { + return MediaProtocol.Rtmp; + } + + if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase)) + { + return MediaProtocol.Http; + } + + if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase)) + { + return MediaProtocol.Rtp; + } + + if (path.StartsWith("ftp", StringComparison.OrdinalIgnoreCase)) + { + return MediaProtocol.Ftp; + } + + if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase)) + { + return MediaProtocol.Udp; + } + + return _fileSystem.IsPathFile(path) ? MediaProtocol.File : MediaProtocol.Http; + } + + public bool SupportsDirectStream(string path, MediaProtocol protocol) + { + if (protocol == MediaProtocol.File) + { + return true; + } + + if (protocol == MediaProtocol.Http) + { + if (path is not null) + { + if (path.Contains(".m3u", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + return true; + } + } + + return false; + } + + private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, CancellationToken cancellationToken) + { + var tasks = _providers.Select(i => GetDynamicMediaSources(item, i, cancellationToken)); + var results = await Task.WhenAll(tasks).ConfigureAwait(false); + + return results.SelectMany(i => i); + } + + private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, IMediaSourceProvider provider, CancellationToken cancellationToken) + { + try + { + var sources = await provider.GetMediaSources(item, cancellationToken).ConfigureAwait(false); + var list = sources.ToList(); + + foreach (var mediaSource in list) + { + mediaSource.InferTotalBitrate(); + + SetKeyProperties(provider, mediaSource); + } + + return list; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting media sources"); + return []; + } + } + + private static void SetKeyProperties(IMediaSourceProvider provider, MediaSourceInfo mediaSource) + { + var prefix = provider.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + LiveStreamIdDelimiter; + + if (!string.IsNullOrEmpty(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + mediaSource.OpenToken = prefix + mediaSource.OpenToken; + } + + if (!string.IsNullOrEmpty(mediaSource.LiveStreamId) && !mediaSource.LiveStreamId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + mediaSource.LiveStreamId = prefix + mediaSource.LiveStreamId; + } + } + + public async Task<MediaSourceInfo> GetMediaSource(BaseItem item, string mediaSourceId, string liveStreamId, bool enablePathSubstitution, CancellationToken cancellationToken) + { + if (!string.IsNullOrEmpty(liveStreamId)) + { + return await GetLiveStream(liveStreamId, cancellationToken).ConfigureAwait(false); + } + + var sources = await GetPlaybackMediaSources(item, null, false, enablePathSubstitution, cancellationToken).ConfigureAwait(false); + + return sources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); + } + + public IReadOnlyList<MediaSourceInfo> GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User user = null) + { + ArgumentNullException.ThrowIfNull(item); + + var hasMediaSources = (IHasMediaSources)item; + + var sources = hasMediaSources.GetMediaSources(enablePathSubstitution); + + if (user is not null) + { + foreach (var source in sources) + { + SetDefaultAudioAndSubtitleStreamIndices(item, source, user); + + if (item.MediaType == MediaType.Audio) + { + source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding); + } + else if (item.MediaType == MediaType.Video) + { + source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding); + source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing); + } + } + } + + return sources; + } + + private IReadOnlyList<string> NormalizeLanguage(string language) + { + if (string.IsNullOrEmpty(language)) + { + return []; + } + + var culture = _localizationManager.FindLanguageInfo(language); + if (culture is not null) + { + return culture.Name.Contains('-', StringComparison.OrdinalIgnoreCase) ? [culture.Name] : culture.ThreeLetterISOLanguageNames; + } + + return [language]; + } + + private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) + { + if (userData is not null + && userData.SubtitleStreamIndex.HasValue + && user.RememberSubtitleSelections + && user.SubtitleMode != SubtitlePlaybackMode.None + && allowRememberingSelection) + { + var index = userData.SubtitleStreamIndex.Value; + // Make sure the saved index is still valid + if (index == -1 || source.MediaStreams.Any(i => i.Type == MediaStreamType.Subtitle && i.Index == index)) + { + source.DefaultSubtitleStreamIndex = index; + return; + } + } + + var preferredSubs = NormalizeLanguage(user.SubtitleLanguagePreference); + + var defaultAudioIndex = source.DefaultAudioStreamIndex; + var audioLanguage = defaultAudioIndex is null + ? null + : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault(); + + source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex( + source.MediaStreams, + preferredSubs, + user.SubtitleMode, + audioLanguage); + + MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLanguage); + } + + private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) + { + if (userData is not null && userData.AudioStreamIndex.HasValue && user.RememberAudioSelections && allowRememberingSelection) + { + var index = userData.AudioStreamIndex.Value; + // Make sure the saved index is still valid + if (source.MediaStreams.Any(i => i.Type == MediaStreamType.Audio && i.Index == index)) + { + source.DefaultAudioStreamIndex = index; + source.DefaultAudioIndexSource = AudioIndexSource.User; + return; + } + } + + var preferredAudio = NormalizeLanguage(user.AudioLanguagePreference); + + source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack); + if (user.PlayDefaultAudioTrack) + { + source.DefaultAudioIndexSource |= AudioIndexSource.Default; + } + + if (preferredAudio.Count > 0) + { + source.DefaultAudioIndexSource |= AudioIndexSource.Language; + } + } + + public void SetDefaultAudioAndSubtitleStreamIndices(BaseItem item, MediaSourceInfo source, User user) + { + // Item would only be null if the app didn't supply ItemId as part of the live stream open request + var mediaType = item?.MediaType ?? MediaType.Video; + + if (mediaType == MediaType.Video) + { + var userData = item is null ? null : _userDataManager.GetUserData(user, item); + + var allowRememberingSelection = item is null || item.EnableRememberingTrackSelections; + + SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection); + SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection); + } + else if (mediaType == MediaType.Audio) + { + var audio = source.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); + + if (audio is not null) + { + source.DefaultAudioStreamIndex = audio.Index; + } + } + } + + private static IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources) + { + return sources.OrderBy(i => + { + if (i.VideoType.HasValue && i.VideoType.Value == VideoType.VideoFile) + { + return 0; + } + + return 1; + }).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0) + .ThenByDescending(i => + { + var stream = i.VideoStream; + + return stream?.Width ?? 0; + }) + .Where(i => i.Type != MediaSourceType.Placeholder); + } + + public async Task<Tuple<LiveStreamResponse, IDirectStreamProvider>> OpenLiveStreamInternal(LiveStreamRequest request, CancellationToken cancellationToken) + { + MediaSourceInfo mediaSource; + ILiveStream liveStream; + + using (await _liveStreamLocker.LockAsync(cancellationToken).ConfigureAwait(false)) + { + var (provider, keyId) = GetProvider(request.OpenToken); + + var currentLiveStreams = _openStreams.Values.ToList(); + + liveStream = await provider.OpenMediaSource(keyId, currentLiveStreams, cancellationToken).ConfigureAwait(false); + + mediaSource = liveStream.MediaSource; + + // Validate that this is actually possible + if (mediaSource.SupportsDirectStream) + { + mediaSource.SupportsDirectStream = SupportsDirectStream(mediaSource.Path, mediaSource.Protocol); + } + + SetKeyProperties(provider, mediaSource); + + _openStreams[mediaSource.LiveStreamId] = liveStream; + } + + try + { + if (mediaSource.MediaStreams.Any(i => i.Index != -1) || !mediaSource.SupportsProbing) + { + AddMediaInfo(mediaSource); + } + else + { + // hack - these two values were taken from LiveTVMediaSourceProvider + string cacheKey = request.OpenToken; + + await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths) + .AddMediaInfoWithProbe(mediaSource, false, cacheKey, true, cancellationToken) + .ConfigureAwait(false); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error probing live tv stream"); + AddMediaInfo(mediaSource); + } + + // TODO: @bond Fix + var json = JsonSerializer.SerializeToUtf8Bytes(mediaSource, _jsonOptions); + _logger.LogInformation("Live stream opened: {@MediaSource}", mediaSource); + var clone = JsonSerializer.Deserialize<MediaSourceInfo>(json, _jsonOptions); + + if (!request.UserId.IsEmpty()) + { + var user = _userManager.GetUserById(request.UserId); + var item = request.ItemId.IsEmpty() + ? null + : _libraryManager.GetItemById(request.ItemId); + SetDefaultAudioAndSubtitleStreamIndices(item, clone, user); + } + + return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse(clone), liveStream as IDirectStreamProvider); + } + + private static void AddMediaInfo(MediaSourceInfo mediaSource) + { + mediaSource.DefaultSubtitleStreamIndex = null; + + // Null this out so that it will be treated like a live stream + if (mediaSource.IsInfiniteStream) + { + mediaSource.RunTimeTicks = null; + } + + var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); + + if (audioStream is null || audioStream.Index == -1) + { + mediaSource.DefaultAudioStreamIndex = null; + } + else + { + mediaSource.DefaultAudioStreamIndex = audioStream.Index; + } + + var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); + if (videoStream is not null) + { + if (!videoStream.BitRate.HasValue) + { + var width = videoStream.Width ?? 1920; + + if (width >= 3000) + { + videoStream.BitRate = 30000000; + } + else if (width >= 1900) + { + videoStream.BitRate = 20000000; + } + else if (width >= 1200) + { + videoStream.BitRate = 8000000; + } + else if (width >= 700) + { + videoStream.BitRate = 2000000; + } + } + } + + // Try to estimate this + mediaSource.InferTotalBitrate(); + } + + public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken) + { + var result = await OpenLiveStreamInternal(request, cancellationToken).ConfigureAwait(false); + return result.Item1; + } + + public async Task<MediaSourceInfo> GetLiveStreamMediaInfo(string id, CancellationToken cancellationToken) + { + // TODO probably shouldn't throw here but it is kept for "backwards compatibility" + var liveStreamInfo = GetLiveStreamInfo(id) ?? throw new ResourceNotFoundException(); + + var mediaSource = liveStreamInfo.MediaSource; + + if (liveStreamInfo is IDirectStreamProvider) + { + var info = await _mediaEncoder.GetMediaInfo( + new MediaInfoRequest + { + MediaSource = mediaSource, + ExtractChapters = false, + MediaType = DlnaProfileType.Video + }, + cancellationToken).ConfigureAwait(false); + + mediaSource.MediaStreams = info.MediaStreams; + mediaSource.Container = info.Container; + mediaSource.Bitrate = info.Bitrate; + } + + return mediaSource; + } + + public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, string cacheKey, bool addProbeDelay, bool isLiveStream, CancellationToken cancellationToken) + { + var originalRuntime = mediaSource.RunTimeTicks; + + var now = DateTime.UtcNow; + + MediaInfo mediaInfo = null; + var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N", CultureInfo.InvariantCulture) + ".json"); + + if (!string.IsNullOrEmpty(cacheKey)) + { + FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath); + try + { + mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Error parsing cached media info."); + } + finally + { + await jsonStream.DisposeAsync().ConfigureAwait(false); + } + } + + if (mediaInfo is null) + { + if (addProbeDelay) + { + var delayMs = mediaSource.AnalyzeDurationMs ?? 0; + delayMs = Math.Max(3000, delayMs); + await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false); + } + + if (isLiveStream) + { + mediaSource.AnalyzeDurationMs = 3000; + } + + mediaInfo = await _mediaEncoder.GetMediaInfo( + new MediaInfoRequest + { + MediaSource = mediaSource, + MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, + ExtractChapters = false + }, + cancellationToken).ConfigureAwait(false); + + if (cacheFilePath is not null) + { + Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); + FileStream createStream = AsyncFile.Create(cacheFilePath); + await using (createStream.ConfigureAwait(false)) + { + await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false); + } + + // _logger.LogDebug("Saved media info to {0}", cacheFilePath); + } + } + + var mediaStreams = mediaInfo.MediaStreams; + + if (isLiveStream && !string.IsNullOrEmpty(cacheKey)) + { + var newList = new List<MediaStream>(); + newList.AddRange(mediaStreams.Where(i => i.Type == MediaStreamType.Video).Take(1)); + newList.AddRange(mediaStreams.Where(i => i.Type == MediaStreamType.Audio).Take(1)); + + foreach (var stream in newList) + { + stream.Index = -1; + stream.Language = null; + } + + mediaStreams = newList; + } + + _logger.LogInformation("Live tv media info probe took {0} seconds", (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture)); + + mediaSource.Bitrate = mediaInfo.Bitrate; + mediaSource.Container = mediaInfo.Container; + mediaSource.Formats = mediaInfo.Formats; + mediaSource.MediaStreams = mediaStreams; + mediaSource.RunTimeTicks = mediaInfo.RunTimeTicks; + mediaSource.Size = mediaInfo.Size; + mediaSource.Timestamp = mediaInfo.Timestamp; + mediaSource.Video3DFormat = mediaInfo.Video3DFormat; + mediaSource.VideoType = mediaInfo.VideoType; + + mediaSource.DefaultSubtitleStreamIndex = null; + + if (isLiveStream) + { + // Null this out so that it will be treated like a live stream + if (!originalRuntime.HasValue) + { + mediaSource.RunTimeTicks = null; + } + } + + var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); + + if (audioStream is null || audioStream.Index == -1) + { + mediaSource.DefaultAudioStreamIndex = null; + } + else + { + mediaSource.DefaultAudioStreamIndex = audioStream.Index; + } + + var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); + if (videoStream is not null) + { + if (!videoStream.BitRate.HasValue) + { + var width = videoStream.Width ?? 1920; + + if (width >= 3000) + { + videoStream.BitRate = 30000000; + } + else if (width >= 1900) + { + videoStream.BitRate = 20000000; + } + else if (width >= 1200) + { + videoStream.BitRate = 8000000; + } + else if (width >= 700) + { + videoStream.BitRate = 2000000; + } + } + + // This is coming up false and preventing stream copy + videoStream.IsAVC = null; + } + + if (isLiveStream) + { + mediaSource.AnalyzeDurationMs = 3000; + } + + // Try to estimate this + mediaSource.InferTotalBitrate(true); + } + + public Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrEmpty(id); + + var info = GetLiveStreamInfo(id); + if (info is null) + { + return Task.FromResult<Tuple<MediaSourceInfo, IDirectStreamProvider>>(new Tuple<MediaSourceInfo, IDirectStreamProvider>(null, null)); + } + + return Task.FromResult<Tuple<MediaSourceInfo, IDirectStreamProvider>>(new Tuple<MediaSourceInfo, IDirectStreamProvider>(info.MediaSource, info as IDirectStreamProvider)); + } + + public ILiveStream GetLiveStreamInfo(string id) + { + ArgumentException.ThrowIfNullOrEmpty(id); + + if (_openStreams.TryGetValue(id, out ILiveStream info)) + { + return info; + } + + return null; + } + + /// <inheritdoc /> + public ILiveStream GetLiveStreamInfoByUniqueId(string uniqueId) + { + return _openStreams.Values.FirstOrDefault(stream => string.Equals(uniqueId, stream?.UniqueId, StringComparison.OrdinalIgnoreCase)); + } + + public async Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken) + { + var result = await GetLiveStreamWithDirectStreamProvider(id, cancellationToken).ConfigureAwait(false); + return result.Item1; + } + + public async Task<IReadOnlyList<MediaSourceInfo>> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken) + { + var stream = new MediaSourceInfo + { + EncoderPath = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveRecordings/" + info.Id + "/stream", + EncoderProtocol = MediaProtocol.Http, + Path = info.Path, + Protocol = MediaProtocol.File, + Id = info.Id, + SupportsDirectPlay = false, + SupportsDirectStream = true, + SupportsTranscoding = true, + IsInfiniteStream = true, + RequiresOpening = false, + RequiresClosing = false, + BufferMs = 0, + IgnoreDts = true, + IgnoreIndex = true + }; + + await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths) + .AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false); + + return [stream]; + } + + public async Task CloseLiveStream(string id) + { + ArgumentException.ThrowIfNullOrEmpty(id); + + using (await _liveStreamLocker.LockAsync().ConfigureAwait(false)) + { + if (_openStreams.TryGetValue(id, out ILiveStream liveStream)) + { + liveStream.ConsumerCount--; + + _logger.LogInformation("Live stream {0} consumer count is now {1}", liveStream.OriginalStreamId, liveStream.ConsumerCount); + + if (liveStream.ConsumerCount <= 0) + { + _openStreams.TryRemove(id, out _); + + _logger.LogInformation("Closing live stream {0}", id); + + await liveStream.Close().ConfigureAwait(false); + _logger.LogInformation("Live stream {0} closed successfully", id); + } + } + } + } + + private (IMediaSourceProvider MediaSourceProvider, string KeyId) GetProvider(string key) + { + ArgumentException.ThrowIfNullOrEmpty(key); + + var keys = key.Split(LiveStreamIdDelimiter, 2); + + var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture), keys[0], StringComparison.OrdinalIgnoreCase)); + + var splitIndex = key.IndexOf(LiveStreamIdDelimiter, StringComparison.Ordinal); + var keyId = key.Substring(splitIndex + 1); + + return (provider, keyId); + } + + /// <inheritdoc /> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> + /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + foreach (var key in _openStreams.Keys.ToList()) + { + CloseLiveStream(key).GetAwaiter().GetResult(); + } + + _liveStreamLocker.Dispose(); + } + } + } +} diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs new file mode 100644 index 00000000..631179ff --- /dev/null +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -0,0 +1,193 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Linq; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Model.Entities; + +namespace Emby.Server.Implementations.Library +{ + public static class MediaStreamSelector + { + public static int? GetDefaultAudioStreamIndex(IReadOnlyList<MediaStream> streams, IReadOnlyList<string> preferredLanguages, bool preferDefaultTrack) + { + var sortedStreams = GetSortedStreams(streams, MediaStreamType.Audio, preferredLanguages).ToList(); + + if (preferDefaultTrack) + { + var defaultStream = sortedStreams.FirstOrDefault(i => i.IsDefault); + + if (defaultStream is not null) + { + return defaultStream.Index; + } + } + + return sortedStreams.FirstOrDefault()?.Index; + } + + public static int? GetDefaultSubtitleStreamIndex( + IEnumerable<MediaStream> streams, + IReadOnlyList<string> preferredLanguages, + SubtitlePlaybackMode mode, + string audioTrackLanguage) + { + if (mode == SubtitlePlaybackMode.None) + { + return null; + } + + // Sort in the following order: Default > No tag > Forced + var sortedStreams = streams + .Where(i => i.Type == MediaStreamType.Subtitle) + .OrderByDescending(x => x.IsExternal) + .ThenByDescending(x => x.IsDefault) + .ThenByDescending(x => !x.IsForced && MatchesPreferredLanguage(x.Language, preferredLanguages)) + .ThenByDescending(x => x.IsForced && MatchesPreferredLanguage(x.Language, preferredLanguages)) + .ThenByDescending(x => x.IsForced && IsLanguageUndefined(x.Language)) + .ThenByDescending(x => x.IsForced) + .ToList(); + + MediaStream? stream = null; + + if (mode == SubtitlePlaybackMode.Default) + { + // Load subtitles according to external, default and forced flags. + stream = sortedStreams.FirstOrDefault(x => x.IsExternal || x.IsDefault || x.IsForced); + } + else if (mode == SubtitlePlaybackMode.Smart) + { + // Only attempt to load subtitles if the audio language is not one of the user's preferred subtitle languages. + // If no subtitles of preferred language available, use none. + // If the audio language is one of the user's preferred subtitle languages behave like OnlyForced. + if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) + { + stream = sortedStreams.FirstOrDefault(x => MatchesPreferredLanguage(x.Language, preferredLanguages)); + } + else + { + stream = BehaviorOnlyForced(sortedStreams, preferredLanguages).FirstOrDefault(); + } + } + else if (mode == SubtitlePlaybackMode.Always) + { + // Always load (full/non-forced) subtitles of the user's preferred subtitle language if possible, otherwise OnlyForced behaviour. + stream = sortedStreams.FirstOrDefault(x => !x.IsForced && MatchesPreferredLanguage(x.Language, preferredLanguages)) ?? + BehaviorOnlyForced(sortedStreams, preferredLanguages).FirstOrDefault(); + } + else if (mode == SubtitlePlaybackMode.OnlyForced) + { + // Load subtitles that are flagged forced of the user's preferred subtitle language or with an undefined language + stream = BehaviorOnlyForced(sortedStreams, preferredLanguages).FirstOrDefault(); + } + + return stream?.Index; + } + + private static IEnumerable<MediaStream> GetSortedStreams(IEnumerable<MediaStream> streams, MediaStreamType type, IReadOnlyList<string> languagePreferences) + { + // Give some preference to external text subs for better performance + return streams + .Where(i => i.Type == type) + .OrderByDescending(i => GetStreamScore(i, languagePreferences)); + } + + public static void SetSubtitleStreamScores( + IReadOnlyList<MediaStream> streams, + IReadOnlyList<string> preferredLanguages, + SubtitlePlaybackMode mode, + string audioTrackLanguage) + { + if (mode == SubtitlePlaybackMode.None) + { + return; + } + + var sortedStreams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages).ToList(); + + List<MediaStream>? filteredStreams = null; + + if (mode == SubtitlePlaybackMode.Default) + { + // Prefer embedded metadata over smart logic + // Load subtitles according to external, default, and forced flags. + filteredStreams = sortedStreams.Where(s => s.IsExternal || s.IsDefault || s.IsForced) + .ToList(); + } + else if (mode == SubtitlePlaybackMode.Smart) + { + // Prefer smart logic over embedded metadata + // Only attempt to load subtitles if the audio language is not one of the user's preferred subtitle languages, otherwise OnlyForced behavior. + if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) + { + filteredStreams = sortedStreams.Where(s => MatchesPreferredLanguage(s.Language, preferredLanguages)) + .ToList(); + } + else + { + filteredStreams = BehaviorOnlyForced(sortedStreams, preferredLanguages); + } + } + else if (mode == SubtitlePlaybackMode.Always) + { + // Always load (full/non-forced) subtitles of the user's preferred subtitle language if possible, otherwise OnlyForced behavior. + filteredStreams = sortedStreams.Where(s => !s.IsForced && MatchesPreferredLanguage(s.Language, preferredLanguages)) + .ToList() ?? BehaviorOnlyForced(sortedStreams, preferredLanguages); + } + else if (mode == SubtitlePlaybackMode.OnlyForced) + { + // Load subtitles that are flagged forced of the user's preferred subtitle language or with an undefined language + filteredStreams = BehaviorOnlyForced(sortedStreams, preferredLanguages); + } + + // If filteredStreams is null, initialize it as an empty list to avoid null reference errors + filteredStreams ??= new List<MediaStream>(); + + foreach (var stream in filteredStreams) + { + stream.Score = GetStreamScore(stream, preferredLanguages); + } + } + + private static bool MatchesPreferredLanguage(string language, IReadOnlyList<string> preferredLanguages) + { + // If preferredLanguages is empty, treat it as "any language" (wildcard) + return preferredLanguages.Count == 0 || + preferredLanguages.Contains(language, StringComparison.OrdinalIgnoreCase); + } + + private static bool IsLanguageUndefined(string language) + { + // Check for null, empty, or known placeholders + return string.IsNullOrEmpty(language) || + language.Equals("und", StringComparison.OrdinalIgnoreCase) || + language.Equals("unknown", StringComparison.OrdinalIgnoreCase) || + language.Equals("undetermined", StringComparison.OrdinalIgnoreCase) || + language.Equals("mul", StringComparison.OrdinalIgnoreCase) || + language.Equals("zxx", StringComparison.OrdinalIgnoreCase); + } + + private static List<MediaStream> BehaviorOnlyForced(IEnumerable<MediaStream> sortedStreams, IReadOnlyList<string> preferredLanguages) + { + return sortedStreams + .Where(s => s.IsForced && (MatchesPreferredLanguage(s.Language, preferredLanguages) || IsLanguageUndefined(s.Language))) + .OrderByDescending(s => MatchesPreferredLanguage(s.Language, preferredLanguages)) + .ThenByDescending(s => IsLanguageUndefined(s.Language)) + .ToList(); + } + + internal static int GetStreamScore(MediaStream stream, IReadOnlyList<string> languagePreferences) + { + var index = languagePreferences.FindIndex(x => string.Equals(x, stream.Language, StringComparison.OrdinalIgnoreCase)); + var score = index == -1 ? 1 : 101 - index; + score = (score * 10) + (stream.IsForced ? 2 : 1); + score = (score * 10) + (stream.IsDefault ? 2 : 1); + score = (score * 10) + (stream.SupportsExternalStream ? 2 : 1); + score = (score * 10) + (stream.IsTextSubtitleStream ? 2 : 1); + score = (score * 10) + (stream.IsExternal ? 2 : 1); + return score; + } + } +} diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs new file mode 100644 index 00000000..e19ad3ef --- /dev/null +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -0,0 +1,135 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; + +namespace Emby.Server.Implementations.Library +{ + public class MusicManager : IMusicManager + { + private readonly ILibraryManager _libraryManager; + + public MusicManager(ILibraryManager libraryManager) + { + _libraryManager = libraryManager; + } + + public IReadOnlyList<BaseItem> GetInstantMixFromSong(Audio item, User? user, DtoOptions dtoOptions) + { + var instantMixItems = GetInstantMixFromGenres(item.Genres, user, dtoOptions); + + return [item, .. instantMixItems.Where(i => !i.Id.Equals(item.Id))]; + } + + /// <inheritdoc /> + public IReadOnlyList<BaseItem> GetInstantMixFromArtist(MusicArtist artist, User? user, DtoOptions dtoOptions) + { + return GetInstantMixFromGenres(artist.Genres, user, dtoOptions); + } + + public IReadOnlyList<BaseItem> GetInstantMixFromAlbum(MusicAlbum item, User? user, DtoOptions dtoOptions) + { + return GetInstantMixFromGenres(item.Genres, user, dtoOptions); + } + + public IReadOnlyList<BaseItem> GetInstantMixFromFolder(Folder item, User? user, DtoOptions dtoOptions) + { + var genres = item + .GetRecursiveChildren( + user, + new InternalItemsQuery(user) + { + IncludeItemTypes = [BaseItemKind.Audio], + DtoOptions = dtoOptions + }, + out _) + .Cast<Audio>() + .SelectMany(i => i.Genres) + .Concat(item.Genres) + .DistinctNames(); + + return GetInstantMixFromGenres(genres, user, dtoOptions); + } + + public IReadOnlyList<BaseItem> GetInstantMixFromPlaylist(Playlist item, User? user, DtoOptions dtoOptions) + { + return GetInstantMixFromGenres(item.Genres, user, dtoOptions); + } + + public IReadOnlyList<BaseItem> GetInstantMixFromGenres(IEnumerable<string> genres, User? user, DtoOptions dtoOptions) + { + var genreIds = genres.DistinctNames().Select(i => + { + try + { + return _libraryManager.GetMusicGenre(i).Id; + } + catch + { + return Guid.Empty; + } + }).Where(i => !i.IsEmpty()).ToArray(); + + return GetInstantMixFromGenreIds(genreIds, user, dtoOptions); + } + + public IReadOnlyList<BaseItem> GetInstantMixFromGenreIds(Guid[] genreIds, User? user, DtoOptions dtoOptions) + { + return _libraryManager.GetItemList(new InternalItemsQuery(user) + { + IncludeItemTypes = [BaseItemKind.Audio], + GenreIds = genreIds, + Limit = 200, + OrderBy = [(ItemSortBy.Random, SortOrder.Ascending)], + DtoOptions = dtoOptions + }); + } + + public IReadOnlyList<BaseItem> GetInstantMixFromItem(BaseItem item, User? user, DtoOptions dtoOptions) + { + if (item is MusicGenre) + { + return GetInstantMixFromGenreIds([item.Id], user, dtoOptions); + } + + if (item is Playlist playlist) + { + return GetInstantMixFromPlaylist(playlist, user, dtoOptions); + } + + if (item is MusicAlbum album) + { + return GetInstantMixFromAlbum(album, user, dtoOptions); + } + + if (item is MusicArtist artist) + { + return GetInstantMixFromArtist(artist, user, dtoOptions); + } + + if (item is Audio song) + { + return GetInstantMixFromSong(song, user, dtoOptions); + } + + if (item is Folder folder) + { + return GetInstantMixFromFolder(folder, user, dtoOptions); + } + + return new List<BaseItem>(); + } + } +} diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs new file mode 100644 index 00000000..fc63251a --- /dev/null +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -0,0 +1,204 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using MediaBrowser.Common.Providers; + +namespace Emby.Server.Implementations.Library +{ + /// <summary> + /// Class providing extension methods for working with paths. + /// </summary> + public static class PathExtensions + { + /// <summary> + /// Gets the attribute value. + /// </summary> + /// <param name="str">The STR.</param> + /// <param name="attribute">The attrib.</param> + /// <returns>System.String.</returns> + /// <exception cref="ArgumentException"><paramref name="str" /> or <paramref name="attribute" /> is empty.</exception> + public static string? GetAttributeValue(this ReadOnlySpan<char> str, ReadOnlySpan<char> attribute) + { + if (str.Length == 0) + { + throw new ArgumentException("String can't be empty.", nameof(str)); + } + + if (attribute.Length == 0) + { + throw new ArgumentException("String can't be empty.", nameof(attribute)); + } + + var attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase); + + // Must be at least 3 characters after the attribute =, ], any character, + // then we offset it by 1, because we want the index and not length. + var maxIndex = str.Length - attribute.Length - 2; + while (attributeIndex > -1 && attributeIndex < maxIndex) + { + var attributeEnd = attributeIndex + attribute.Length; + if (attributeIndex > 0) + { + var attributeOpener = str[attributeIndex - 1]; + var attributeCloser = attributeOpener switch + { + '[' => ']', + '(' => ')', + '{' => '}', + _ => '\0' + }; + if (attributeCloser != '\0' && (str[attributeEnd] == '=' || str[attributeEnd] == '-')) + { + var closingIndex = str[attributeEnd..].IndexOf(attributeCloser); + + // Must be at least 1 character before the closing bracket. + if (closingIndex > 1) + { + return str[(attributeEnd + 1)..(attributeEnd + closingIndex)].Trim().ToString(); + } + } + } + + str = str[attributeEnd..]; + attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase); + } + + // for imdbid we also accept pattern matching + if (attribute.Equals("imdbid", StringComparison.OrdinalIgnoreCase)) + { + var match = ProviderIdParsers.TryFindImdbId(str, out var imdbId); + return match ? imdbId.ToString() : null; + } + + return null; + } + + /// <summary> + /// Replaces a sub path with another sub path and normalizes the final path. + /// </summary> + /// <param name="path">The original path.</param> + /// <param name="subPath">The original sub path.</param> + /// <param name="newSubPath">The new sub path.</param> + /// <param name="newPath">The result of the sub path replacement.</param> + /// <returns>The path after replacing the sub path.</returns> + /// <exception cref="ArgumentNullException"><paramref name="path" />, <paramref name="newSubPath" /> or <paramref name="newSubPath" /> is empty.</exception> + public static bool TryReplaceSubPath( + [NotNullWhen(true)] this string? path, + [NotNullWhen(true)] string? subPath, + [NotNullWhen(true)] string? newSubPath, + [NotNullWhen(true)] out string? newPath) + { + newPath = null; + + if (string.IsNullOrEmpty(path) + || string.IsNullOrEmpty(subPath) + || string.IsNullOrEmpty(newSubPath) + || subPath.Length > path.Length) + { + return false; + } + + subPath = subPath.NormalizePath(out var newDirectorySeparatorChar); + path = path.NormalizePath(newDirectorySeparatorChar); + + // We have to ensure that the sub path ends with a directory separator otherwise we'll get weird results + // when the sub path matches a similar but in-complete subpath + var oldSubPathEndsWithSeparator = subPath[^1] == newDirectorySeparatorChar; + if (!path.StartsWith(subPath, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (path.Length > subPath.Length + && !oldSubPathEndsWithSeparator + && path[subPath.Length] != newDirectorySeparatorChar) + { + return false; + } + + var newSubPathTrimmed = newSubPath.AsSpan().TrimEnd(newDirectorySeparatorChar); + // Ensure that the path with the old subpath removed starts with a leading dir separator + int idx = oldSubPathEndsWithSeparator ? subPath.Length - 1 : subPath.Length; + newPath = string.Concat(newSubPathTrimmed, path.AsSpan(idx)); + + return true; + } + + /// <summary> + /// Retrieves the full resolved path and normalizes path separators to the <see cref="Path.DirectorySeparatorChar"/>. + /// </summary> + /// <param name="path">The path to canonicalize.</param> + /// <returns>The fully expanded, normalized path.</returns> + public static string Canonicalize(this string path) + { + return Path.GetFullPath(path).NormalizePath(); + } + + /// <summary> + /// Normalizes the path's directory separator character to the currently defined <see cref="Path.DirectorySeparatorChar"/>. + /// </summary> + /// <param name="path">The path to normalize.</param> + /// <returns>The normalized path string or <see langword="null"/> if the input path is null or empty.</returns> + [return: NotNullIfNotNull(nameof(path))] + public static string? NormalizePath(this string? path) + { + return path.NormalizePath(Path.DirectorySeparatorChar); + } + + /// <summary> + /// Normalizes the path's directory separator character. + /// </summary> + /// <param name="path">The path to normalize.</param> + /// <param name="separator">The separator character the path now uses or <see langword="null"/>.</param> + /// <returns>The normalized path string or <see langword="null"/> if the input path is null or empty.</returns> + [return: NotNullIfNotNull(nameof(path))] + public static string? NormalizePath(this string? path, out char separator) + { + if (string.IsNullOrEmpty(path)) + { + separator = default; + return path; + } + + var newSeparator = '\\'; + + // True normalization is still not possible https://github.com/dotnet/runtime/issues/2162 + // The reasoning behind this is that a forward slash likely means it's a Linux path and + // so the whole path should be normalized to use / and vice versa for Windows (although Windows doesn't care much). + if (path.Contains('/', StringComparison.Ordinal)) + { + newSeparator = '/'; + } + + separator = newSeparator; + + return path.NormalizePath(newSeparator); + } + + /// <summary> + /// Normalizes the path's directory separator character to the specified character. + /// </summary> + /// <param name="path">The path to normalize.</param> + /// <param name="newSeparator">The replacement directory separator character. Must be a valid directory separator.</param> + /// <returns>The normalized path.</returns> + /// <exception cref="ArgumentException">Thrown if the new separator character is not a directory separator.</exception> + [return: NotNullIfNotNull(nameof(path))] + public static string? NormalizePath(this string? path, char newSeparator) + { + const char Bs = '\\'; + const char Fs = '/'; + + if (!(newSeparator == Bs || newSeparator == Fs)) + { + throw new ArgumentException("The character must be a directory separator."); + } + + if (string.IsNullOrEmpty(path)) + { + return path; + } + + return newSeparator == Bs ? path.Replace(Fs, newSeparator) : path.Replace(Bs, newSeparator); + } + } +} diff --git a/Emby.Server.Implementations/Library/PathManager.cs b/Emby.Server.Implementations/Library/PathManager.cs new file mode 100644 index 00000000..a9b7a127 --- /dev/null +++ b/Emby.Server.Implementations/Library/PathManager.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; + +namespace Emby.Server.Implementations.Library; + +/// <summary> +/// IPathManager implementation. +/// </summary> +public class PathManager : IPathManager +{ + private readonly IServerConfigurationManager _config; + private readonly IApplicationPaths _appPaths; + + /// <summary> + /// Initializes a new instance of the <see cref="PathManager"/> class. + /// </summary> + /// <param name="config">The server configuration manager.</param> + /// <param name="appPaths">The application paths.</param> + public PathManager( + IServerConfigurationManager config, + IApplicationPaths appPaths) + { + _config = config; + _appPaths = appPaths; + } + + private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles"); + + private string AttachmentCachePath => Path.Combine(_appPaths.DataPath, "attachments"); + + /// <inheritdoc /> + public string GetAttachmentPath(string mediaSourceId, string fileName) + { + return Path.Combine(GetAttachmentFolderPath(mediaSourceId), fileName); + } + + /// <inheritdoc /> + public string GetAttachmentFolderPath(string mediaSourceId) + { + var id = Guid.Parse(mediaSourceId).ToString("D", CultureInfo.InvariantCulture).AsSpan(); + + return Path.Join(AttachmentCachePath, id[..2], id); + } + + /// <inheritdoc /> + public string GetSubtitleFolderPath(string mediaSourceId) + { + var id = Guid.Parse(mediaSourceId).ToString("D", CultureInfo.InvariantCulture).AsSpan(); + + return Path.Join(SubtitleCachePath, id[..2], id); + } + + /// <inheritdoc /> + public string GetSubtitlePath(string mediaSourceId, int streamIndex, string extension) + { + return Path.Combine(GetSubtitleFolderPath(mediaSourceId), streamIndex.ToString(CultureInfo.InvariantCulture) + extension); + } + + /// <inheritdoc /> + public string GetTrickplayDirectory(BaseItem item, bool saveWithMedia = false) + { + var id = item.Id.ToString("D", CultureInfo.InvariantCulture).AsSpan(); + + return saveWithMedia + ? Path.Combine(item.ContainingFolderPath, Path.ChangeExtension(Path.GetFileName(item.Path), ".trickplay")) + : Path.Join(_config.ApplicationPaths.TrickplayPath, id[..2], id); + } + + /// <inheritdoc/> + public string GetChapterImageFolderPath(BaseItem item) + { + return Path.Combine(item.GetInternalMetadataPath(), "chapters"); + } + + /// <inheritdoc/> + public string GetChapterImagePath(BaseItem item, long chapterPositionTicks) + { + var filename = item.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) + "_" + chapterPositionTicks.ToString(CultureInfo.InvariantCulture) + ".jpg"; + + return Path.Combine(GetChapterImageFolderPath(item), filename); + } + + /// <inheritdoc/> + public IReadOnlyList<string> GetExtractedDataPaths(BaseItem item) + { + var mediaSourceId = item.Id.ToString("N", CultureInfo.InvariantCulture); + return [ + GetAttachmentFolderPath(mediaSourceId), + GetSubtitleFolderPath(mediaSourceId), + GetTrickplayDirectory(item, false), + GetTrickplayDirectory(item, true), + GetChapterImageFolderPath(item) + ]; + } +} diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs new file mode 100644 index 00000000..06aa772b --- /dev/null +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -0,0 +1,168 @@ +using System; +using System.IO; +using System.Linq; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.Library +{ + /// <summary> + /// Class ResolverHelper. + /// </summary> + public static class ResolverHelper + { + /// <summary> + /// Sets the initial item values. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="parent">The parent.</param> + /// <param name="libraryManager">The library manager.</param> + /// <param name="directoryService">The directory service.</param> + /// <returns>True if initializing was successful.</returns> + /// <exception cref="ArgumentException">Item must have a path.</exception> + public static bool SetInitialItemValues(BaseItem item, Folder? parent, ILibraryManager libraryManager, IDirectoryService directoryService) + { + // This version of the below method has no ItemResolveArgs, so we have to require the path already being set + ArgumentException.ThrowIfNullOrEmpty(item.Path); + + // If the resolver didn't specify this + if (parent is not null) + { + item.SetParent(parent); + } + + item.Id = libraryManager.GetNewItemId(item.Path, item.GetType()); + + item.IsLocked = item.Path.Contains("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) || + item.GetParents().Any(i => i.IsLocked); + + // Make sure DateCreated and DateModified have values + var fileInfo = directoryService.GetFileSystemEntry(item.Path); + if (fileInfo is null) + { + return false; + } + + SetDateCreated(item, fileInfo); + + EnsureName(item, fileInfo); + + return true; + } + + /// <summary> + /// Sets the initial item values. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="args">The args.</param> + /// <param name="fileSystem">The file system.</param> + /// <param name="libraryManager">The library manager.</param> + public static void SetInitialItemValues(BaseItem item, ItemResolveArgs args, IFileSystem fileSystem, ILibraryManager libraryManager) + { + // If the resolver didn't specify this + if (string.IsNullOrEmpty(item.Path)) + { + item.Path = args.Path; + } + + // If the resolver didn't specify this + if (args.Parent is not null) + { + item.SetParent(args.Parent); + } + + item.Id = libraryManager.GetNewItemId(item.Path, item.GetType()); + + // Make sure the item has a name + EnsureName(item, args.FileInfo); + + item.IsLocked = item.Path.Contains("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) || + item.GetParents().Any(i => i.IsLocked); + + // Make sure DateCreated and DateModified have values + EnsureDates(fileSystem, item, args); + } + + /// <summary> + /// Ensures the name. + /// </summary> + private static void EnsureName(BaseItem item, FileSystemMetadata fileInfo) + { + // If the subclass didn't supply a name, add it here + if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(item.Path)) + { + item.Name = fileInfo.IsDirectory ? fileInfo.Name : Path.GetFileNameWithoutExtension(fileInfo.Name); + } + } + + /// <summary> + /// Ensures DateCreated and DateModified have values. + /// </summary> + /// <param name="fileSystem">The file system.</param> + /// <param name="item">The item.</param> + /// <param name="args">The args.</param> + private static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args) + { + // See if a different path came out of the resolver than what went in + if (!fileSystem.AreEqual(args.Path, item.Path)) + { + var childData = args.IsDirectory ? args.GetFileSystemEntryByPath(item.Path) : null; + + if (childData is not null) + { + SetDateCreated(item, childData); + } + else + { + var fileData = fileSystem.GetFileSystemInfo(item.Path); + + if (fileData.Exists) + { + SetDateCreated(item, fileData); + } + } + } + else + { + SetDateCreated(item, args.FileInfo); + } + } + + private static void SetDateCreated(BaseItem item, FileSystemMetadata? info) + { + var config = BaseItem.ConfigurationManager.GetMetadataConfiguration(); + + if (config.UseFileCreationTimeForDateAdded) + { + var fileCreationDate = info?.CreationTimeUtc; + if (fileCreationDate is not null) + { + var dateCreated = fileCreationDate; + if (dateCreated == DateTime.MinValue) + { + dateCreated = DateTime.UtcNow; + } + + item.DateCreated = dateCreated.Value; + } + } + else + { + item.DateCreated = DateTime.UtcNow; + } + + if (info is not null && !info.IsDirectory) + { + item.Size = info.Length; + } + + var fileModificationDate = info?.LastWriteTimeUtc; + if (fileModificationDate.HasValue) + { + item.DateModified = fileModificationDate.Value; + } + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs new file mode 100644 index 00000000..dbf05c1d --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -0,0 +1,240 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Emby.Naming.Audio; +using Emby.Naming.AudioBook; +using Emby.Naming.Common; +using Emby.Naming.Video; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.Library.Resolvers.Audio +{ + /// <summary> + /// Class AudioResolver. + /// </summary> + public class AudioResolver : ItemResolver<MediaBrowser.Controller.Entities.Audio.Audio>, IMultiItemResolver + { + private readonly NamingOptions _namingOptions; + + public AudioResolver(NamingOptions namingOptions) + { + _namingOptions = namingOptions; + } + + /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + public override ResolverPriority Priority => ResolverPriority.Fifth; + + public MultiItemResolverResult ResolveMultiple( + Folder parent, + List<FileSystemMetadata> files, + CollectionType? collectionType, + IDirectoryService directoryService) + { + var result = ResolveMultipleInternal(parent, files, collectionType); + + if (result is not null) + { + foreach (var item in result.Items) + { + SetInitialItemValues((MediaBrowser.Controller.Entities.Audio.Audio)item, null); + } + } + + return result; + } + + private MultiItemResolverResult ResolveMultipleInternal( + Folder parent, + List<FileSystemMetadata> files, + CollectionType? collectionType) + { + if (collectionType == CollectionType.books) + { + return ResolveMultipleAudio(parent, files, true); + } + + return null; + } + + /// <summary> + /// Resolves the specified args. + /// </summary> + /// <param name="args">The args.</param> + /// <returns>Entities.Audio.Audio.</returns> + protected override MediaBrowser.Controller.Entities.Audio.Audio Resolve(ItemResolveArgs args) + { + // Return audio if the path is a file and has a matching extension + + var collectionType = args.GetCollectionType(); + + var isBooksCollectionType = collectionType == CollectionType.books; + + if (args.IsDirectory) + { + if (!isBooksCollectionType) + { + return null; + } + + return FindAudioBook(args, false); + } + + if (AudioFileParser.IsAudioFile(args.Path, _namingOptions)) + { + var extension = Path.GetExtension(args.Path.AsSpan()); + + if (extension.Equals(".cue", StringComparison.OrdinalIgnoreCase)) + { + // if audio file exists of same name, return null + return null; + } + + var isMixedCollectionType = collectionType is null; + + // For conflicting extensions, give priority to videos + if (isMixedCollectionType && VideoResolver.IsVideoFile(args.Path, _namingOptions)) + { + return null; + } + + MediaBrowser.Controller.Entities.Audio.Audio item = null; + + var isMusicCollectionType = collectionType == CollectionType.music; + + // Use regular audio type for mixed libraries, owned items and music + if (isMixedCollectionType || + args.Parent is null || + isMusicCollectionType) + { + item = new MediaBrowser.Controller.Entities.Audio.Audio(); + } + else if (isBooksCollectionType) + { + item = new AudioBook(); + } + + if (item is not null) + { + item.IsShortcut = extension.Equals(".strm", StringComparison.OrdinalIgnoreCase); + + item.IsInMixedFolder = true; + } + + return item; + } + + return null; + } + + private AudioBook FindAudioBook(ItemResolveArgs args, bool parseName) + { + // TODO: Allow GetMultiDiscMovie in here + var result = ResolveMultipleAudio(args.Parent, args.GetActualFileSystemChildren(), parseName); + + if (result is null || result.Items.Count != 1 || result.Items[0] is not AudioBook item) + { + return null; + } + + // If we were supporting this we'd be checking filesFromOtherItems + item.IsInMixedFolder = false; + item.Name = Path.GetFileName(item.ContainingFolderPath); + return item; + } + + private MultiItemResolverResult ResolveMultipleAudio(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, bool parseName) + { + var files = new List<FileSystemMetadata>(); + var leftOver = new List<FileSystemMetadata>(); + + // Loop through each child file/folder and see if we find a video + foreach (var child in fileSystemEntries) + { + if (child.IsDirectory) + { + leftOver.Add(child); + } + else + { + files.Add(child); + } + } + + var resolver = new AudioBookListResolver(_namingOptions); + var resolverResult = resolver.Resolve(files).ToList(); + + var result = new MultiItemResolverResult + { + ExtraFiles = leftOver, + Items = new List<BaseItem>() + }; + + var isInMixedFolder = resolverResult.Count > 1 || (parent is not null && parent.IsTopParent); + + foreach (var resolvedItem in resolverResult) + { + if (resolvedItem.Files.Count > 1) + { + // For now, until we sort out naming for multi-part books + continue; + } + + // Until multi-part books are handled letting files stack hides them from browsing in the client + if (resolvedItem.Files.Count == 0 || resolvedItem.Extras.Count > 0 || resolvedItem.AlternateVersions.Count > 0) + { + continue; + } + + var firstMedia = resolvedItem.Files[0]; + + var libraryItem = new AudioBook + { + Path = firstMedia.Path, + IsInMixedFolder = isInMixedFolder, + ProductionYear = resolvedItem.Year, + Name = parseName ? + resolvedItem.Name : + Path.GetFileNameWithoutExtension(firstMedia.Path), + // AdditionalParts = resolvedItem.Files.Skip(1).Select(i => i.Path).ToArray(), + // LocalAlternateVersions = resolvedItem.AlternateVersions.Select(i => i.Path).ToArray() + }; + + result.Items.Add(libraryItem); + } + + result.ExtraFiles.AddRange(files.Where(i => !ContainsFile(resolverResult, i))); + + return result; + } + + private static bool ContainsFile(IEnumerable<AudioBookInfo> result, FileSystemMetadata file) + { + return result.Any(i => ContainsFile(i, file)); + } + + private static bool ContainsFile(AudioBookInfo result, FileSystemMetadata file) + { + return result.Files.Any(i => ContainsFile(i, file)) || + result.AlternateVersions.Any(i => ContainsFile(i, file)) || + result.Extras.Any(i => ContainsFile(i, file)); + } + + private static bool ContainsFile(AudioBookFileInfo result, FileSystemMetadata file) + { + return string.Equals(result.Path, file.FullName, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs new file mode 100644 index 00000000..9405f210 --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -0,0 +1,181 @@ +#nullable disable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Emby.Naming.Audio; +using Emby.Naming.Common; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Model.IO; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library.Resolvers.Audio +{ + /// <summary> + /// The music album resolver. + /// </summary> + public class MusicAlbumResolver : ItemResolver<MusicAlbum> + { + private readonly ILogger<MusicAlbumResolver> _logger; + private readonly NamingOptions _namingOptions; + private readonly IDirectoryService _directoryService; + + /// <summary> + /// Initializes a new instance of the <see cref="MusicAlbumResolver"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="namingOptions">The naming options.</param> + /// <param name="directoryService">The directory service.</param> + public MusicAlbumResolver(ILogger<MusicAlbumResolver> logger, NamingOptions namingOptions, IDirectoryService directoryService) + { + _logger = logger; + _namingOptions = namingOptions; + _directoryService = directoryService; + } + + /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + public override ResolverPriority Priority => ResolverPriority.Third; + + /// <summary> + /// Resolves the specified args. + /// </summary> + /// <param name="args">The args.</param> + /// <returns>MusicAlbum.</returns> + protected override MusicAlbum Resolve(ItemResolveArgs args) + { + var collectionType = args.GetCollectionType(); + var isMusicMediaFolder = collectionType == CollectionType.music; + + // If there's a collection type and it's not music, don't allow it. + if (!isMusicMediaFolder) + { + return null; + } + + if (!args.IsDirectory) + { + return null; + } + + // Avoid mis-identifying top folders + if (args.HasParent<MusicAlbum>()) + { + return null; + } + + if (args.Parent.IsRoot) + { + return null; + } + + return IsMusicAlbum(args) ? new MusicAlbum() : null; + } + + /// <summary> + /// Determine if the supplied file data points to a music album. + /// </summary> + /// <param name="path">The path to check.</param> + /// <param name="directoryService">The directory service.</param> + /// <returns><c>true</c> if the provided path points to a music album; otherwise, <c>false</c>.</returns> + public bool IsMusicAlbum(string path, IDirectoryService directoryService) + { + return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService); + } + + /// <summary> + /// Determine if the supplied resolve args should be considered a music album. + /// </summary> + /// <param name="args">The args.</param> + /// <returns><c>true</c> if [is music album] [the specified args]; otherwise, <c>false</c>.</returns> + private bool IsMusicAlbum(ItemResolveArgs args) + { + if (args.IsDirectory) + { + // If args is a artist subfolder it's not a music album + foreach (var subfolder in _namingOptions.ArtistSubfolders) + { + if (Path.GetDirectoryName(args.Path.AsSpan()).Equals(subfolder, StringComparison.OrdinalIgnoreCase)) + { + _logger.LogDebug("Found release folder: {Path}", args.Path); + return false; + } + } + + // If args contains music it's a music album + if (ContainsMusic(args.FileSystemChildren, true, _directoryService)) + { + return true; + } + } + + return false; + } + + /// <summary> + /// Determine if the supplied list contains what we should consider music. + /// </summary> + /// <returns><c>true</c> if the provided path list contains music; otherwise, <c>false</c>.</returns> + private bool ContainsMusic( + ICollection<FileSystemMetadata> list, + bool allowSubfolders, + IDirectoryService directoryService) + { + // Check for audio files before digging down into directories + var foundAudioFile = list.Any(fileSystemInfo => !fileSystemInfo.IsDirectory && AudioFileParser.IsAudioFile(fileSystemInfo.FullName, _namingOptions)); + if (foundAudioFile) + { + // At least one audio file exists + return true; + } + + if (!allowSubfolders) + { + // Not music since no audio file exists and we're not looking into subfolders + return false; + } + + var discSubfolderCount = 0; + + var parser = new AlbumParser(_namingOptions); + + var directories = list.Where(fileSystemInfo => fileSystemInfo.IsDirectory); + + var result = Parallel.ForEach(directories, (fileSystemInfo, state) => + { + var path = fileSystemInfo.FullName; + var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService); + + if (hasMusic) + { + if (parser.IsMultiPart(path)) + { + _logger.LogDebug("Found multi-disc folder: {Path}", path); + Interlocked.Increment(ref discSubfolderCount); + } + else + { + // If there are folders underneath with music that are not multidisc, then this can't be a multi-disc album + state.Stop(); + } + } + }); + + if (!result.IsCompleted) + { + return false; + } + + return discSubfolderCount > 0; + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs new file mode 100644 index 00000000..f7270bec --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -0,0 +1,122 @@ +#nullable disable + +using System; +using System.Linq; +using System.Threading.Tasks; +using Emby.Naming.Audio; +using Emby.Naming.Common; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Model.Entities; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library.Resolvers.Audio +{ + /// <summary> + /// The music artist resolver. + /// </summary> + public class MusicArtistResolver : ItemResolver<MusicArtist> + { + private readonly ILogger<MusicAlbumResolver> _logger; + private readonly NamingOptions _namingOptions; + private readonly IDirectoryService _directoryService; + + /// <summary> + /// Initializes a new instance of the <see cref="MusicArtistResolver"/> class. + /// </summary> + /// <param name="logger">Instance of the <see cref="MusicAlbumResolver"/> interface.</param> + /// <param name="namingOptions">The <see cref="NamingOptions"/>.</param> + /// <param name="directoryService">The directory service.</param> + public MusicArtistResolver( + ILogger<MusicAlbumResolver> logger, + NamingOptions namingOptions, + IDirectoryService directoryService) + { + _logger = logger; + _namingOptions = namingOptions; + _directoryService = directoryService; + } + + /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + public override ResolverPriority Priority => ResolverPriority.Second; + + /// <summary> + /// Resolves the specified resolver arguments. + /// </summary> + /// <param name="args">The resolver arguments.</param> + /// <returns>A <see cref="MusicArtist"/>.</returns> + protected override MusicArtist Resolve(ItemResolveArgs args) + { + if (!args.IsDirectory) + { + return null; + } + + // Don't allow nested artists + if (args.HasParent<MusicArtist>() || args.HasParent<MusicAlbum>()) + { + return null; + } + + var collectionType = args.GetCollectionType(); + + var isMusicMediaFolder = collectionType == CollectionType.music; + + // If there's a collection type and it's not music, it can't be a music artist + if (!isMusicMediaFolder) + { + return null; + } + + if (args.ContainsFileSystemEntryByName("artist.nfo")) + { + return new MusicArtist(); + } + + // Avoid mis-identifying top folders + if (args.Parent.IsRoot) + { + return null; + } + + var albumResolver = new MusicAlbumResolver(_logger, _namingOptions, _directoryService); + var albumParser = new AlbumParser(_namingOptions); + + var directories = args.FileSystemChildren.Where(i => i.IsDirectory); + + var result = Parallel.ForEach(directories, (fileSystemInfo, state) => + { + // If we contain a artist subfolder assume we are an artist folder + foreach (var subfolder in _namingOptions.ArtistSubfolders) + { + if (fileSystemInfo.Name.Equals(subfolder, StringComparison.OrdinalIgnoreCase)) + { + // Stop once we see an artist subfolder + state.Stop(); + } + } + + // If the folder is a multi-disc folder, then it is not an artist folder + if (albumParser.IsMultiPart(fileSystemInfo.FullName)) + { + return; + } + + // If we contain a music album assume we are an artist folder + if (albumResolver.IsMusicAlbum(fileSystemInfo.FullName, _directoryService)) + { + // Stop once we see a music album + state.Stop(); + } + }); + + return !result.IsCompleted ? new MusicArtist() : null; + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs new file mode 100644 index 00000000..779cfd5b --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -0,0 +1,289 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using System.IO; +using System.Linq; +using DiscUtils.Udf; +using Emby.Naming.Common; +using Emby.Naming.Video; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library.Resolvers +{ + /// <summary> + /// Resolves a Path into a Video or Video subclass. + /// </summary> + /// <typeparam name="T">The type of item to resolve.</typeparam> + public abstract class BaseVideoResolver<T> : MediaBrowser.Controller.Resolvers.ItemResolver<T> + where T : Video, new() + { + private readonly ILogger _logger; + + protected BaseVideoResolver(ILogger logger, NamingOptions namingOptions, IDirectoryService directoryService) + { + _logger = logger; + NamingOptions = namingOptions; + DirectoryService = directoryService; + } + + protected NamingOptions NamingOptions { get; } + + protected IDirectoryService DirectoryService { get; } + + /// <summary> + /// Resolves the specified args. + /// </summary> + /// <param name="args">The args.</param> + /// <returns>`0.</returns> + protected override T Resolve(ItemResolveArgs args) + { + return ResolveVideo<T>(args, false); + } + + /// <summary> + /// Resolves the video. + /// </summary> + /// <typeparam name="TVideoType">The type of the T video type.</typeparam> + /// <param name="args">The args.</param> + /// <param name="parseName">if set to <c>true</c> [parse name].</param> + /// <returns>``0.</returns> + protected virtual TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName) + where TVideoType : Video, new() + { + VideoFileInfo videoInfo = null; + VideoType? videoType = null; + + // If the path is a file check for a matching extensions + if (args.IsDirectory) + { + // Loop through each child file/folder and see if we find a video + foreach (var child in args.FileSystemChildren) + { + var filename = child.Name; + if (child.IsDirectory) + { + if (IsDvdDirectory(child.FullName, filename, DirectoryService)) + { + var videoTmp = new TVideoType + { + Path = args.Path, + VideoType = VideoType.Dvd + }; + Set3DFormat(videoTmp); + return videoTmp; + } + + if (IsBluRayDirectory(filename)) + { + var videoTmp = new TVideoType + { + Path = args.Path, + VideoType = VideoType.BluRay + }; + Set3DFormat(videoTmp); + return videoTmp; + } + } + else if (IsDvdFile(filename)) + { + videoType = VideoType.Dvd; + } + + if (videoType is null) + { + continue; + } + + videoInfo = VideoResolver.ResolveDirectory(args.Path, NamingOptions, parseName); + break; + } + } + else + { + videoInfo = VideoResolver.Resolve(args.Path, false, NamingOptions, parseName); + } + + if (videoInfo is 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) + { + var extension = Path.GetExtension(video.Path.AsSpan()); + video.VideoType = extension.Equals(".iso", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".img", StringComparison.OrdinalIgnoreCase) + ? VideoType.Iso + : VideoType.VideoFile; + + video.IsShortcut = extension.Equals(".strm", StringComparison.OrdinalIgnoreCase); + video.IsPlaceHolder = videoInfo.IsStub; + + if (videoInfo.IsStub) + { + if (string.Equals(videoInfo.StubType, "dvd", StringComparison.OrdinalIgnoreCase)) + { + video.VideoType = VideoType.Dvd; + } + else if (string.Equals(videoInfo.StubType, "bluray", StringComparison.OrdinalIgnoreCase)) + { + video.VideoType = VideoType.BluRay; + } + } + + SetIsoType(video); + } + + protected void SetIsoType(Video video) + { + if (video.VideoType == VideoType.Iso) + { + if (video.Path.Contains("dvd", StringComparison.OrdinalIgnoreCase)) + { + video.IsoType = IsoType.Dvd; + } + else if (video.Path.Contains("bluray", StringComparison.OrdinalIgnoreCase)) + { + video.IsoType = IsoType.BluRay; + } + else + { + try + { + // use disc-utils, both DVDs and BDs use UDF filesystem + using var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read, FileShare.Read); + using UdfReader udfReader = new UdfReader(videoFileStream); + if (udfReader.DirectoryExists("VIDEO_TS")) + { + video.IsoType = IsoType.Dvd; + } + else if (udfReader.DirectoryExists("BDMV")) + { + video.IsoType = IsoType.BluRay; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error opening UDF/ISO image: {Value}", video.Path ?? video.Name); + } + } + } + } + + protected void Set3DFormat(Video video, bool is3D, string format3D) + { + if (is3D) + { + if (string.Equals(format3D, "fsbs", StringComparison.OrdinalIgnoreCase)) + { + video.Video3DFormat = Video3DFormat.FullSideBySide; + } + else if (string.Equals(format3D, "ftab", StringComparison.OrdinalIgnoreCase)) + { + video.Video3DFormat = Video3DFormat.FullTopAndBottom; + } + else if (string.Equals(format3D, "hsbs", StringComparison.OrdinalIgnoreCase)) + { + video.Video3DFormat = Video3DFormat.HalfSideBySide; + } + else if (string.Equals(format3D, "htab", StringComparison.OrdinalIgnoreCase)) + { + video.Video3DFormat = Video3DFormat.HalfTopAndBottom; + } + else if (string.Equals(format3D, "sbs", StringComparison.OrdinalIgnoreCase)) + { + video.Video3DFormat = Video3DFormat.HalfSideBySide; + } + else if (string.Equals(format3D, "sbs3d", StringComparison.OrdinalIgnoreCase)) + { + video.Video3DFormat = Video3DFormat.HalfSideBySide; + } + else if (string.Equals(format3D, "tab", StringComparison.OrdinalIgnoreCase)) + { + video.Video3DFormat = Video3DFormat.HalfTopAndBottom; + } + else if (string.Equals(format3D, "mvc", StringComparison.OrdinalIgnoreCase)) + { + video.Video3DFormat = Video3DFormat.MVC; + } + } + } + + protected void Set3DFormat(Video video, VideoFileInfo videoInfo) + { + Set3DFormat(video, videoInfo.Is3D, videoInfo.Format3D); + } + + protected void Set3DFormat(Video video) + { + var result = Format3DParser.Parse(video.Path, NamingOptions); + + Set3DFormat(video, result.Is3D, result.Format3D); + } + + /// <summary> + /// Determines whether [is DVD directory] [the specified directory name]. + /// </summary> + /// <param name="fullPath">The full path of the directory.</param> + /// <param name="directoryName">The name of the directory.</param> + /// <param name="directoryService">The directory service.</param> + /// <returns><c>true</c> if the provided directory is a DVD directory, <c>false</c> otherwise.</returns> + protected bool IsDvdDirectory(string fullPath, string directoryName, IDirectoryService directoryService) + { + if (!string.Equals(directoryName, "video_ts", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + return directoryService.GetFilePaths(fullPath).Any(i => Path.GetExtension(i.AsSpan()).Equals(".vob", StringComparison.OrdinalIgnoreCase)); + } + + /// <summary> + /// Determines whether [is DVD file] [the specified name]. + /// </summary> + /// <param name="name">The name.</param> + /// <returns><c>true</c> if [is DVD file] [the specified name]; otherwise, <c>false</c>.</returns> + protected bool IsDvdFile(string name) + { + return string.Equals(name, "video_ts.ifo", StringComparison.OrdinalIgnoreCase); + } + + /// <summary> + /// Determines whether [is bluray directory] [the specified directory name]. + /// </summary> + /// <param name="directoryName">The directory name.</param> + /// <returns>Whether the directory is a bluray directory.</returns> + protected bool IsBluRayDirectory(string directoryName) + { + return string.Equals(directoryName, "bdmv", StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs new file mode 100644 index 00000000..1e885aad --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs @@ -0,0 +1,86 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using System.IO; +using System.Linq; +using Emby.Naming.Book; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Resolvers; + +namespace Emby.Server.Implementations.Library.Resolvers.Books +{ + public class BookResolver : ItemResolver<Book> + { + private readonly string[] _validExtensions = { ".azw", ".azw3", ".cb7", ".cbr", ".cbt", ".cbz", ".epub", ".mobi", ".pdf" }; + + protected override Book Resolve(ItemResolveArgs args) + { + var collectionType = args.GetCollectionType(); + + // Only process items that are in a collection folder containing books + if (collectionType != CollectionType.books) + { + return null; + } + + if (args.IsDirectory) + { + return GetBook(args); + } + + var extension = Path.GetExtension(args.Path.AsSpan()); + + if (!_validExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + var result = BookFileNameParser.Parse(Path.GetFileNameWithoutExtension(args.Path)); + + return new Book + { + Path = args.Path, + Name = result.Name ?? string.Empty, + IndexNumber = result.Index, + ProductionYear = result.Year, + SeriesName = result.SeriesName ?? Path.GetFileName(Path.GetDirectoryName(args.Path)), + IsInMixedFolder = true, + }; + } + + private Book GetBook(ItemResolveArgs args) + { + var bookFiles = args.FileSystemChildren.Where(f => + { + var fileExtension = Path.GetExtension(f.FullName.AsSpan()); + + return _validExtensions.Contains( + fileExtension, + StringComparison.OrdinalIgnoreCase); + }).ToList(); + + // directory is only considered a book when it contains exactly one supported file + // other library structures with multiple books to a directory will get picked up as individual files + if (bookFiles.Count != 1) + { + return null; + } + + var result = BookFileNameParser.Parse(Path.GetFileName(args.Path)); + + return new Book + { + Path = bookFiles[0].FullName, + Name = result.Name ?? string.Empty, + IndexNumber = result.Index, + ProductionYear = result.Year, + SeriesName = result.SeriesName ?? string.Empty, + }; + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs new file mode 100644 index 00000000..b9f9f297 --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs @@ -0,0 +1,104 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using Emby.Naming.Common; +using Emby.Naming.Video; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Model.Entities; +using Microsoft.Extensions.Logging; +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 : BaseVideoResolver<Video> + { + private readonly NamingOptions _namingOptions; + private readonly IItemResolver[] _trailerResolvers; + private readonly IItemResolver[] _videoResolvers; + + /// <summary> + /// Initializes a new instance of the <see cref="ExtraResolver"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="namingOptions">An instance of <see cref="NamingOptions"/>.</param> + /// <param name="directoryService">The directory service.</param> + public ExtraResolver(ILogger<ExtraResolver> logger, NamingOptions namingOptions, IDirectoryService directoryService) + : base(logger, namingOptions, directoryService) + { + _namingOptions = namingOptions; + _trailerResolvers = new IItemResolver[] { new GenericVideoResolver<Trailer>(logger, namingOptions, directoryService) }; + _videoResolvers = new IItemResolver[] { this }; + } + + protected override Video Resolve(ItemResolveArgs args) + { + return ResolveVideo<Video>(args, true); + } + + /// <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, string? libraryRoot = "") + { + var extraResult = GetExtraInfo(path, _namingOptions, libraryRoot); + if (extraResult.ExtraType is 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); + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs new file mode 100644 index 00000000..db7703cd --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs @@ -0,0 +1,35 @@ +#nullable disable + +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Resolvers; + +namespace Emby.Server.Implementations.Library.Resolvers +{ + /// <summary> + /// Class FolderResolver. + /// </summary> + public class FolderResolver : GenericFolderResolver<Folder> + { + /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + public override ResolverPriority Priority => ResolverPriority.Last; + + /// <summary> + /// Resolves the specified args. + /// </summary> + /// <param name="args">The args.</param> + /// <returns>Folder.</returns> + protected override Folder Resolve(ItemResolveArgs args) + { + if (args.IsDirectory) + { + return new Folder(); + } + + return null; + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs new file mode 100644 index 00000000..1c2de912 --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs @@ -0,0 +1,28 @@ +#nullable disable + +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Resolvers; + +namespace Emby.Server.Implementations.Library.Resolvers +{ + /// <summary> + /// Class FolderResolver. + /// </summary> + /// <typeparam name="TItemType">The type of the T item type.</typeparam> + public abstract class GenericFolderResolver<TItemType> : ItemResolver<TItemType> + where TItemType : Folder, new() + { + /// <summary> + /// Sets the initial item values. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="args">The args.</param> + protected override void SetInitialItemValues(TItemType item, ItemResolveArgs args) + { + base.SetInitialItemValues(item, args); + + item.IsRoot = args.Parent is null; + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs new file mode 100644 index 00000000..ba320266 --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs @@ -0,0 +1,28 @@ +#nullable disable + +using Emby.Naming.Common; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library.Resolvers +{ + /// <summary> + /// Resolves a Path into an instance of the <see cref="Video"/> class. + /// </summary> + /// <typeparam name="T">The type of item to resolve.</typeparam> + public class GenericVideoResolver<T> : BaseVideoResolver<T> + where T : Video, new() + { + /// <summary> + /// Initializes a new instance of the <see cref="GenericVideoResolver{T}"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="namingOptions">The naming options.</param> + /// <param name="directoryService">The directory service.</param> + public GenericVideoResolver(ILogger logger, NamingOptions namingOptions, IDirectoryService directoryService) + : base(logger, namingOptions, directoryService) + { + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs new file mode 100644 index 00000000..4b150738 --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs @@ -0,0 +1,74 @@ +#nullable disable + +using System; +using System.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; + +namespace Emby.Server.Implementations.Library.Resolvers.Movies +{ + /// <summary> + /// Class BoxSetResolver. + /// </summary> + public class BoxSetResolver : GenericFolderResolver<BoxSet> + { + /// <summary> + /// Resolves the specified args. + /// </summary> + /// <param name="args">The args.</param> + /// <returns>BoxSet.</returns> + protected override BoxSet Resolve(ItemResolveArgs args) + { + // It's a boxset if all of the following conditions are met: + // Is a Directory + // Contains [boxset] in the path + if (args.IsDirectory) + { + var filename = Path.GetFileName(args.Path); + + if (string.IsNullOrEmpty(filename)) + { + return null; + } + + if (filename.Contains("[boxset]", StringComparison.OrdinalIgnoreCase) || args.ContainsFileSystemEntryByName("collection.xml")) + { + return new BoxSet + { + Path = args.Path, + Name = Path.GetFileName(args.Path).Replace("[boxset]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim() + }; + } + } + + return null; + } + + /// <summary> + /// Sets the initial item values. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="args">The args.</param> + protected override void SetInitialItemValues(BoxSet item, ItemResolveArgs args) + { + base.SetInitialItemValues(item, args); + + SetProviderIdFromPath(item); + } + + /// <summary> + /// Sets the provider id from path. + /// </summary> + /// <param name="item">The item.</param> + private static void SetProviderIdFromPath(BaseItem item) + { + // we need to only look at the name of this actual item (not parents) + var justName = Path.GetFileName(item.Path.AsSpan()); + + var id = justName.GetAttributeValue("tmdbid"); + item.TrySetProviderId(MetadataProvider.Tmdb, id); + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs new file mode 100644 index 00000000..98e8f535 --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -0,0 +1,586 @@ +#nullable disable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using Emby.Naming.Common; +using Emby.Naming.Video; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library.Resolvers.Movies +{ + /// <summary> + /// Class MovieResolver. + /// </summary> + public partial class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver + { + private readonly IImageProcessor _imageProcessor; + + private static readonly CollectionType[] _validCollectionTypes = new[] + { + CollectionType.movies, + CollectionType.homevideos, + CollectionType.musicvideos, + CollectionType.tvshows, + CollectionType.photos + }; + + /// <summary> + /// Initializes a new instance of the <see cref="MovieResolver"/> class. + /// </summary> + /// <param name="imageProcessor">The image processor.</param> + /// <param name="logger">The logger.</param> + /// <param name="namingOptions">The naming options.</param> + /// <param name="directoryService">The directory service.</param> + public MovieResolver(IImageProcessor imageProcessor, ILogger<MovieResolver> logger, NamingOptions namingOptions, IDirectoryService directoryService) + : base(logger, namingOptions, directoryService) + { + _imageProcessor = imageProcessor; + } + + /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + public override ResolverPriority Priority => ResolverPriority.Fourth; + + [GeneratedRegex(@"\bsample\b", RegexOptions.IgnoreCase)] + private static partial Regex IsIgnoredRegex(); + + /// <inheritdoc /> + public MultiItemResolverResult ResolveMultiple( + Folder parent, + List<FileSystemMetadata> files, + CollectionType? collectionType, + IDirectoryService directoryService) + { + var result = ResolveMultipleInternal(parent, files, collectionType); + + if (result is not null) + { + foreach (var item in result.Items) + { + SetInitialItemValues((Video)item, null); + } + } + + return result; + } + + /// <summary> + /// Resolves the specified args. + /// </summary> + /// <param name="args">The args.</param> + /// <returns>Video.</returns> + protected override Video Resolve(ItemResolveArgs args) + { + var collectionType = args.GetCollectionType(); + + // Find movies with their own folders + if (args.IsDirectory) + { + if (IsInvalid(args.Parent, collectionType)) + { + return null; + } + + Video movie = null; + var files = args.GetActualFileSystemChildren().ToList(); + + if (collectionType == CollectionType.musicvideos) + { + movie = FindMovie<MusicVideo>(args, args.Path, args.Parent, files, DirectoryService, collectionType, false); + } + + if (collectionType == CollectionType.homevideos) + { + movie = FindMovie<Video>(args, args.Path, args.Parent, files, DirectoryService, collectionType, false); + } + + if (collectionType is null) + { + // Owned items will be caught by the video extra resolver + if (args.Parent is null) + { + return null; + } + + if (args.HasParent<Series>()) + { + return null; + } + + movie = FindMovie<Movie>(args, args.Path, args.Parent, files, DirectoryService, collectionType, true); + } + + if (collectionType == CollectionType.movies) + { + movie = FindMovie<Movie>(args, args.Path, args.Parent, files, DirectoryService, collectionType, true); + } + + // ignore extras + return movie?.ExtraType is null ? movie : null; + } + + if (args.Parent is null) + { + return base.Resolve(args); + } + + if (IsInvalid(args.Parent, collectionType)) + { + return null; + } + + Video item = null; + + if (collectionType == CollectionType.musicvideos) + { + item = ResolveVideo<MusicVideo>(args, false); + } + + // To find a movie file, the collection type must be movies or boxsets + else if (collectionType == CollectionType.movies) + { + item = ResolveVideo<Movie>(args, true); + } + else if (collectionType == CollectionType.homevideos || collectionType == CollectionType.photos) + { + item = ResolveVideo<Video>(args, false); + } + else if (collectionType is null) + { + if (args.HasParent<Series>()) + { + return null; + } + + item = ResolveVideo<Video>(args, false); + } + + // Ignore extras + if (item?.ExtraType is not null) + { + return null; + } + + if (item is not null) + { + item.IsInMixedFolder = true; + } + + return item; + } + + private MultiItemResolverResult ResolveMultipleInternal( + Folder parent, + List<FileSystemMetadata> files, + CollectionType? collectionType) + { + if (IsInvalid(parent, collectionType)) + { + return null; + } + + if (collectionType is CollectionType.musicvideos) + { + return ResolveVideos<MusicVideo>(parent, files, true, collectionType, false); + } + + if (collectionType == CollectionType.homevideos || collectionType == CollectionType.photos) + { + return ResolveVideos<Video>(parent, files, false, collectionType, false); + } + + if (collectionType is null) + { + // Owned items should just use the plain video type + if (parent is null) + { + return ResolveVideos<Video>(parent, files, false, collectionType, false); + } + + if (parent is Series || parent.GetParents().OfType<Series>().Any()) + { + return null; + } + + return ResolveVideos<Movie>(parent, files, false, collectionType, true); + } + + if (collectionType == CollectionType.movies) + { + return ResolveVideos<Movie>(parent, files, true, collectionType, true); + } + + if (collectionType == CollectionType.tvshows) + { + return ResolveVideos<Episode>(parent, files, false, collectionType, true); + } + + return null; + } + + private MultiItemResolverResult ResolveVideos<T>( + Folder parent, + IEnumerable<FileSystemMetadata> fileSystemEntries, + bool supportMultiEditions, + CollectionType? collectionType, + bool parseName) + where T : Video, new() + { + var files = new List<FileSystemMetadata>(); + var leftOver = new List<FileSystemMetadata>(); + var hasCollectionType = collectionType is not null; + + // Loop through each child file/folder and see if we find a video + foreach (var child in fileSystemEntries) + { + // This is a hack but currently no better way to resolve a sometimes ambiguous situation + if (!hasCollectionType) + { + if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase) + || string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + } + + if (child.IsDirectory) + { + leftOver.Add(child); + } + else if (!IsIgnoredRegex().IsMatch(child.Name)) + { + files.Add(child); + } + } + + var videoInfos = files + .Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, NamingOptions, parseName, parent.ContainingFolderPath)) + .Where(f => f is not null) + .ToList(); + + var resolverResult = VideoListResolver.Resolve(videoInfos, NamingOptions, supportMultiEditions, parseName, parent.ContainingFolderPath); + + var result = new MultiItemResolverResult + { + ExtraFiles = leftOver + }; + + var isInMixedFolder = resolverResult.Count > 1 || parent?.IsTopParent == true; + + foreach (var video in resolverResult) + { + var firstVideo = video.Files[0]; + var path = firstVideo.Path; + if (video.ExtraType is not null) + { + result.ExtraFiles.Add(files.Find(f => string.Equals(f.FullName, path, StringComparison.OrdinalIgnoreCase))); + continue; + } + + var additionalParts = video.Files.Count > 1 ? video.Files.Skip(1).Select(i => i.Path).ToArray() : Array.Empty<string>(); + + var videoItem = new T + { + Path = path, + IsInMixedFolder = isInMixedFolder, + ProductionYear = video.Year, + Name = parseName ? video.Name : firstVideo.Name, + AdditionalParts = additionalParts, + LocalAlternateVersions = video.AlternateVersions.Select(i => i.Path).ToArray() + }; + + SetVideoType(videoItem, firstVideo); + Set3DFormat(videoItem, firstVideo); + + result.Items.Add(videoItem); + } + + result.ExtraFiles.AddRange(files.Where(i => !ContainsFile(resolverResult, i))); + + return result; + } + + private static bool ContainsFile(IReadOnlyList<VideoInfo> result, FileSystemMetadata 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; + } + } + + for (var j = 0; j < current.AlternateVersions.Count; j++) + { + if (ContainsFile(current.AlternateVersions[j], file)) + { + return true; + } + } + } + + return false; + } + + private static bool ContainsFile(VideoFileInfo result, FileSystemMetadata file) + { + return string.Equals(result.Path, file.FullName, StringComparison.OrdinalIgnoreCase); + } + + /// <summary> + /// Sets the initial item values. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="args">The args.</param> + protected override void SetInitialItemValues(Video item, ItemResolveArgs args) + { + base.SetInitialItemValues(item, args); + + SetProviderIdsFromPath(item); + } + + /// <summary> + /// Sets the provider id from path. + /// </summary> + /// <param name="item">The item.</param> + private static void SetProviderIdsFromPath(Video item) + { + if (item is Movie || item is MusicVideo) + { + // We need to only look at the name of this actual item (not parents) + var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path.AsSpan()) : Path.GetFileName(item.ContainingFolderPath.AsSpan()); + + var tmdbid = justName.GetAttributeValue("tmdbid"); + + // If not in a mixed folder and ID not found in folder path, check filename + if (string.IsNullOrEmpty(tmdbid) && !item.IsInMixedFolder) + { + tmdbid = Path.GetFileName(item.Path.AsSpan()).GetAttributeValue("tmdbid"); + } + + item.TrySetProviderId(MetadataProvider.Tmdb, tmdbid); + + 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 (whether id in parent dir or in file name) + var imdbid = item.Path.AsSpan().GetAttributeValue("imdbid"); + item.TrySetProviderId(MetadataProvider.Imdb, imdbid); + } + } + } + + /// <summary> + /// Finds a movie based on a child file system entries. + /// </summary> + /// <returns>Movie.</returns> + private T FindMovie<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, CollectionType? collectionType, bool parseName) + where T : Video, new() + { + var multiDiscFolders = new List<FileSystemMetadata>(); + + var libraryOptions = args.LibraryOptions; + var supportPhotos = collectionType == CollectionType.homevideos && libraryOptions.EnablePhotos; + var photos = new List<FileSystemMetadata>(); + + // Search for a folder rip + foreach (var child in fileSystemEntries) + { + var filename = child.Name; + + if (child.IsDirectory) + { + if (NamingOptions.AllExtrasTypesFolderNames.ContainsKey(filename)) + { + continue; + } + + if (IsDvdDirectory(child.FullName, filename, directoryService)) + { + var movie = new T + { + Path = path, + VideoType = VideoType.Dvd + }; + Set3DFormat(movie); + return movie; + } + + if (IsBluRayDirectory(filename)) + { + var movie = new T + { + Path = path, + VideoType = VideoType.BluRay + }; + Set3DFormat(movie); + return movie; + } + + multiDiscFolders.Add(child); + } + else if (IsDvdFile(filename)) + { + var movie = new T + { + Path = path, + VideoType = VideoType.Dvd + }; + Set3DFormat(movie); + return movie; + } + else if (supportPhotos && PhotoResolver.IsImageFile(child.FullName, _imageProcessor)) + { + photos.Add(child); + } + } + + // TODO: Allow GetMultiDiscMovie in here + const bool SupportsMultiVersion = true; + + var result = ResolveVideos<T>(parent, fileSystemEntries, SupportsMultiVersion, collectionType, parseName) ?? + new MultiItemResolverResult(); + + var isPhotosCollection = collectionType == CollectionType.homevideos || collectionType == CollectionType.photos; + if (!isPhotosCollection && result.Items.Count == 1) + { + var videoPath = result.Items[0].Path; + var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(videoPath, i.Name)); + var hasOtherSubfolders = multiDiscFolders.Count > 0; + + if (!hasPhotos && !hasOtherSubfolders) + { + var movie = (T)result.Items[0]; + movie.IsInMixedFolder = false; + if (collectionType == CollectionType.movies || collectionType is null) + { + movie.Name = Path.GetFileName(movie.ContainingFolderPath); + } + + return movie; + } + } + else if (result.Items.Count == 0 && multiDiscFolders.Count > 0) + { + return GetMultiDiscMovie<T>(multiDiscFolders, directoryService); + } + + return null; + } + + /// <summary> + /// Gets the multi disc movie. + /// </summary> + /// <param name="multiDiscFolders">The folders.</param> + /// <param name="directoryService">The directory service.</param> + /// <returns>``0.</returns> + private T GetMultiDiscMovie<T>(List<FileSystemMetadata> multiDiscFolders, IDirectoryService directoryService) + where T : Video, new() + { + var videoTypes = new List<VideoType>(); + + var folderPaths = multiDiscFolders.Select(i => i.FullName).Where(i => + { + var subFileEntries = directoryService.GetFileSystemEntries(i); + + var subfolders = subFileEntries + .Where(e => e.IsDirectory) + .ToList(); + + if (subfolders.Any(s => IsDvdDirectory(s.FullName, s.Name, directoryService))) + { + videoTypes.Add(VideoType.Dvd); + return true; + } + + if (subfolders.Any(s => IsBluRayDirectory(s.Name))) + { + videoTypes.Add(VideoType.BluRay); + return true; + } + + var subFiles = subFileEntries + .Where(e => !e.IsDirectory) + .Select(d => d.Name); + + if (subFiles.Any(IsDvdFile)) + { + videoTypes.Add(VideoType.Dvd); + return true; + } + + return false; + }).Order().ToList(); + + // If different video types were found, don't allow this + if (videoTypes.Distinct().Count() > 1) + { + return null; + } + + if (folderPaths.Count == 0) + { + return null; + } + + var result = StackResolver.ResolveDirectories(folderPaths, NamingOptions).ToList(); + + if (result.Count != 1) + { + return null; + } + + int additionalPartsLen = folderPaths.Count - 1; + var additionalParts = new string[additionalPartsLen]; + folderPaths.CopyTo(1, additionalParts, 0, additionalPartsLen); + + var returnVideo = new T + { + Path = folderPaths[0], + AdditionalParts = additionalParts, + VideoType = videoTypes[0], + Name = result[0].Name + }; + + SetIsoType(returnVideo); + + return returnVideo; + } + + private bool IsInvalid(Folder parent, CollectionType? collectionType) + { + if (parent is not null) + { + if (parent.IsRoot) + { + return true; + } + } + + if (collectionType is null) + { + return false; + } + + return !_validCollectionTypes.Contains(collectionType.Value); + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs new file mode 100644 index 00000000..c0b00caa --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs @@ -0,0 +1,95 @@ +#nullable disable + +using System; +using Emby.Naming.Common; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Model.Entities; + +namespace Emby.Server.Implementations.Library.Resolvers +{ + /// <summary> + /// Class PhotoAlbumResolver. + /// </summary> + public class PhotoAlbumResolver : GenericFolderResolver<PhotoAlbum> + { + private readonly IImageProcessor _imageProcessor; + private readonly NamingOptions _namingOptions; + + /// <summary> + /// Initializes a new instance of the <see cref="PhotoAlbumResolver"/> class. + /// </summary> + /// <param name="imageProcessor">The image processor.</param> + /// <param name="namingOptions">The naming options.</param> + public PhotoAlbumResolver(IImageProcessor imageProcessor, NamingOptions namingOptions) + { + _imageProcessor = imageProcessor; + _namingOptions = namingOptions; + } + + /// <inheritdoc /> + public override ResolverPriority Priority => ResolverPriority.Second; + + /// <summary> + /// Resolves the specified args. + /// </summary> + /// <param name="args">The args.</param> + /// <returns>Trailer.</returns> + protected override PhotoAlbum Resolve(ItemResolveArgs args) + { + // Must be an image file within a photo collection + if (args.IsDirectory) + { + // Must be an image file within a photo collection + var collectionType = args.GetCollectionType(); + + if (collectionType == CollectionType.photos + || (collectionType == CollectionType.homevideos && args.LibraryOptions.EnablePhotos)) + { + if (HasPhotos(args)) + { + return new PhotoAlbum + { + Path = args.Path + }; + } + } + } + + return null; + } + + private bool HasPhotos(ItemResolveArgs args) + { + var files = args.FileSystemChildren; + + foreach (var file in files) + { + if (!file.IsDirectory && PhotoResolver.IsImageFile(file.FullName, _imageProcessor)) + { + var filename = file.Name; + var ownedByMedia = false; + + foreach (var siblingFile in files) + { + if (PhotoResolver.IsOwnedByMedia(_namingOptions, siblingFile.FullName, filename)) + { + ownedByMedia = true; + break; + } + } + + if (!ownedByMedia) + { + return true; + } + } + } + + return false; + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs new file mode 100644 index 00000000..0934555b --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -0,0 +1,122 @@ +using System; +using System.IO; +using System.Linq; +using Emby.Naming.Common; +using Emby.Naming.Video; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Model.Entities; + +namespace Emby.Server.Implementations.Library.Resolvers +{ + /// <summary> + /// Class PhotoResolver. + /// </summary> + public class PhotoResolver : ItemResolver<Photo> + { + private readonly IImageProcessor _imageProcessor; + private readonly NamingOptions _namingOptions; + private readonly IDirectoryService _directoryService; + + private static readonly string[] _ignoreFiles = new[] + { + "folder", + "thumb", + "landscape", + "fanart", + "backdrop", + "poster", + "cover", + "logo", + "default" + }; + + /// <summary> + /// Initializes a new instance of the <see cref="PhotoResolver"/> class. + /// </summary> + /// <param name="imageProcessor">The image processor.</param> + /// <param name="namingOptions">The naming options.</param> + /// <param name="directoryService">The directory service.</param> + public PhotoResolver(IImageProcessor imageProcessor, NamingOptions namingOptions, IDirectoryService directoryService) + { + _imageProcessor = imageProcessor; + _namingOptions = namingOptions; + _directoryService = directoryService; + } + + /// <summary> + /// Resolves the specified args. + /// </summary> + /// <param name="args">The args.</param> + /// <returns>Trailer.</returns> + protected override Photo? Resolve(ItemResolveArgs args) + { + if (!args.IsDirectory) + { + // Must be an image file within a photo collection + var collectionType = args.CollectionType; + + if (collectionType == CollectionType.photos + || (collectionType == CollectionType.homevideos && args.LibraryOptions.EnablePhotos)) + { + if (IsImageFile(args.Path, _imageProcessor)) + { + var filename = Path.GetFileNameWithoutExtension(args.Path.AsSpan()); + + // Make sure the image doesn't belong to a video file + var files = _directoryService.GetFiles(Path.GetDirectoryName(args.Path) + ?? throw new InvalidOperationException("Path can't be a root directory.")); + + foreach (var file in files) + { + if (IsOwnedByMedia(_namingOptions, file.FullName, filename)) + { + return null; + } + } + + return new Photo + { + Path = args.Path + }; + } + } + } + + return null; + } + + internal static bool IsOwnedByMedia(NamingOptions namingOptions, string file, ReadOnlySpan<char> imageFilename) + { + return VideoResolver.IsVideoFile(file, namingOptions) && IsOwnedByResolvedMedia(file, imageFilename); + } + + internal static bool IsOwnedByResolvedMedia(ReadOnlySpan<char> file, ReadOnlySpan<char> imageFilename) + => imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase); + + internal static bool IsImageFile(string path, IImageProcessor imageProcessor) + { + ArgumentNullException.ThrowIfNull(path); + + var extension = Path.GetExtension(path.AsSpan()).TrimStart('.'); + if (!imageProcessor.SupportedInputFormats.Contains(extension, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + var filename = Path.GetFileNameWithoutExtension(path); + + if (_ignoreFiles.Any(i => filename.StartsWith(i, StringComparison.OrdinalIgnoreCase))) + { + return false; + } + + return true; + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs new file mode 100644 index 00000000..14798dda --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -0,0 +1,82 @@ +#nullable disable + +using System; +using System.IO; +using System.Linq; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.LocalMetadata.Savers; + +namespace Emby.Server.Implementations.Library.Resolvers +{ + /// <summary> + /// <see cref="IItemResolver"/> for <see cref="Playlist"/> library items. + /// </summary> + public class PlaylistResolver : GenericFolderResolver<Playlist> + { + private readonly CollectionType?[] _musicPlaylistCollectionTypes = + [ + null, + CollectionType.music + ]; + + /// <inheritdoc/> + protected override Playlist Resolve(ItemResolveArgs args) + { + if (args.IsDirectory) + { + // It's a playlist if the path is a directory with [playlist] in its name + var filename = Path.GetFileName(Path.TrimEndingDirectorySeparator(args.Path)); + if (string.IsNullOrEmpty(filename)) + { + return null; + } + + if (filename.Contains("[playlist]", StringComparison.OrdinalIgnoreCase)) + { + return new Playlist + { + Path = args.Path, + Name = filename.Replace("[playlist]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim(), + OpenAccess = true + }; + } + + // It's a directory-based playlist if the directory contains a playlist file + var filePaths = Directory.EnumerateFiles(args.Path, "*", new EnumerationOptions { IgnoreInaccessible = true }); + if (filePaths.Any(f => f.EndsWith(PlaylistXmlSaver.DefaultPlaylistFilename, StringComparison.OrdinalIgnoreCase))) + { + return new Playlist + { + Path = args.Path, + Name = filename, + OpenAccess = true + }; + } + } + + // Check if this is a music playlist file + // It should have the correct collection type and a supported file extension + else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType)) + { + var extension = Path.GetExtension(args.Path.AsSpan()); + if (Playlist.SupportedExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) + { + return new Playlist + { + Path = args.Path, + Name = Path.GetFileNameWithoutExtension(args.Path), + IsInMixedFolder = true, + PlaylistMediaType = MediaType.Audio, + OpenAccess = true + }; + } + } + + return null; + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs new file mode 100644 index 00000000..3d91ed24 --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs @@ -0,0 +1,86 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using System.IO; +using System.Linq; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.Library.Resolvers +{ + public class SpecialFolderResolver : GenericFolderResolver<Folder> + { + private readonly IFileSystem _fileSystem; + private readonly IServerApplicationPaths _appPaths; + + public SpecialFolderResolver(IFileSystem fileSystem, IServerApplicationPaths appPaths) + { + _fileSystem = fileSystem; + _appPaths = appPaths; + } + + /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + public override ResolverPriority Priority => ResolverPriority.First; + + /// <summary> + /// Resolves the specified args. + /// </summary> + /// <param name="args">The args.</param> + /// <returns>Folder.</returns> + protected override Folder Resolve(ItemResolveArgs args) + { + if (args.IsDirectory) + { + if (args.IsPhysicalRoot) + { + return new AggregateFolder(); + } + + if (string.Equals(args.Path, _appPaths.DefaultUserViewsPath, StringComparison.OrdinalIgnoreCase)) + { + return new UserRootFolder(); // if we got here and still a root - must be user root + } + + if (args.IsVf) + { + return new CollectionFolder + { + CollectionType = GetCollectionType(args), + PhysicalLocationsList = args.PhysicalLocations + }; + } + } + + return null; + } + + private CollectionType? GetCollectionType(ItemResolveArgs args) + { + return args.FileSystemChildren + .Where(i => + { + try + { + return !i.IsDirectory && + string.Equals(".collection", i.Extension, StringComparison.OrdinalIgnoreCase); + } + catch (IOException) + { + return false; + } + }) + .Select(i => _fileSystem.GetFileNameWithoutExtension(i)) + .Select(i => Enum.TryParse<CollectionType>(i, out var collectionType) ? collectionType : (CollectionType?)null) + .FirstOrDefault(i => i is not null); + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs new file mode 100644 index 00000000..5fd23c9f --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -0,0 +1,90 @@ +#nullable disable + +using System; +using System.Linq; +using Emby.Naming.Common; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library.Resolvers.TV +{ + /// <summary> + /// Class EpisodeResolver. + /// </summary> + public class EpisodeResolver : BaseVideoResolver<Episode> + { + /// <summary> + /// Initializes a new instance of the <see cref="EpisodeResolver"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="namingOptions">The naming options.</param> + /// <param name="directoryService">The directory service.</param> + public EpisodeResolver(ILogger<EpisodeResolver> logger, NamingOptions namingOptions, IDirectoryService directoryService) + : base(logger, namingOptions, directoryService) + { + } + + /// <summary> + /// Resolves the specified args. + /// </summary> + /// <param name="args">The args.</param> + /// <returns>Episode.</returns> + protected override Episode Resolve(ItemResolveArgs args) + { + var parent = args.Parent; + + if (parent is null) + { + return null; + } + + // Just in case the user decided to nest episodes. + // Not officially supported but in some cases we can handle it. + + var season = parent as Season ?? parent.GetParents().OfType<Season>().FirstOrDefault(); + + // If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something + // Also handle flat tv folders + if (season is not null + || args.GetCollectionType() == CollectionType.tvshows + || args.HasParent<Series>()) + { + var episode = ResolveVideo<Episode>(args, false); + + // Ignore extras + if (episode is null || episode.ExtraType is not null) + { + return null; + } + + var series = parent as Series ?? parent.GetParents().OfType<Series>().FirstOrDefault(); + + if (series is not null) + { + episode.SeriesId = series.Id; + episode.SeriesName = series.Name; + } + + if (season is not null) + { + episode.SeasonId = season.Id; + episode.SeasonName = season.Name; + } + + // Assume season 1 if there's no season folder and a season number could not be determined + if (season is null && !episode.ParentIndexNumber.HasValue && (episode.IndexNumber.HasValue || episode.PremiereDate.HasValue)) + { + episode.ParentIndexNumber = 1; + } + + return episode; + } + + return null; + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs new file mode 100644 index 00000000..6cb63a28 --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -0,0 +1,100 @@ +#nullable disable + +using System.Globalization; +using Emby.Naming.Common; +using Emby.Naming.TV; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Globalization; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library.Resolvers.TV +{ + /// <summary> + /// Class SeasonResolver. + /// </summary> + public class SeasonResolver : GenericFolderResolver<Season> + { + private readonly ILocalizationManager _localization; + private readonly ILogger<SeasonResolver> _logger; + private readonly NamingOptions _namingOptions; + + /// <summary> + /// Initializes a new instance of the <see cref="SeasonResolver"/> class. + /// </summary> + /// <param name="namingOptions">The naming options.</param> + /// <param name="localization">The localization.</param> + /// <param name="logger">The logger.</param> + public SeasonResolver( + NamingOptions namingOptions, + ILocalizationManager localization, + ILogger<SeasonResolver> logger) + { + _namingOptions = namingOptions; + _localization = localization; + _logger = logger; + } + + /// <summary> + /// Resolves the specified args. + /// </summary> + /// <param name="args">The args.</param> + /// <returns>Season.</returns> + protected override Season Resolve(ItemResolveArgs args) + { + if (args.Parent is Series series && args.IsDirectory) + { + var namingOptions = _namingOptions; + + var path = args.Path; + + var seasonParserResult = SeasonPathParser.Parse(path, series.ContainingFolderPath, true, true); + + var season = new Season + { + IndexNumber = seasonParserResult.SeasonNumber, + SeriesId = series.Id, + SeriesName = series.Name, + Path = seasonParserResult.IsSeasonFolder ? path : null + }; + + if (!season.IndexNumber.HasValue || !seasonParserResult.IsSeasonFolder) + { + var resolver = new Naming.TV.EpisodeResolver(namingOptions); + + var folderName = System.IO.Path.GetFileName(path); + var testPath = @"\\test\" + folderName; + + var episodeInfo = resolver.Resolve(testPath, true); + + if (episodeInfo?.EpisodeNumber is not null && episodeInfo.SeasonNumber.HasValue) + { + _logger.LogDebug( + "Found folder underneath series with episode number: {0}. Season {1}. Episode {2}", + path, + episodeInfo.SeasonNumber.Value, + episodeInfo.EpisodeNumber.Value); + + return null; + } + } + + if (season.IndexNumber.HasValue && string.IsNullOrEmpty(season.Name)) + { + var seasonNumber = season.IndexNumber.Value; + season.Name = seasonNumber == 0 ? + args.LibraryOptions.SeasonZeroDisplayName : + string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("NameSeasonNumber"), + seasonNumber, + args.LibraryOptions.PreferredMetadataLanguage); + } + + return season; + } + + return null; + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs new file mode 100644 index 00000000..c81a0adb --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -0,0 +1,211 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.IO; +using Emby.Naming.Common; +using Emby.Naming.TV; +using Emby.Naming.Video; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library.Resolvers.TV +{ + /// <summary> + /// Class SeriesResolver. + /// </summary> + public class SeriesResolver : GenericFolderResolver<Series> + { + private readonly ILogger<SeriesResolver> _logger; + private readonly NamingOptions _namingOptions; + + /// <summary> + /// Initializes a new instance of the <see cref="SeriesResolver"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="namingOptions">The naming options.</param> + public SeriesResolver(ILogger<SeriesResolver> logger, NamingOptions namingOptions) + { + _logger = logger; + _namingOptions = namingOptions; + } + + /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + public override ResolverPriority Priority => ResolverPriority.Second; + + /// <summary> + /// Resolves the specified args. + /// </summary> + /// <param name="args">The args.</param> + /// <returns>Series.</returns> + protected override Series Resolve(ItemResolveArgs args) + { + if (args.IsDirectory) + { + if (args.HasParent<Series>() || args.HasParent<Season>()) + { + return null; + } + + var seriesInfo = Naming.TV.SeriesResolver.Resolve(_namingOptions, args.Path); + + var collectionType = args.GetCollectionType(); + if (collectionType == CollectionType.tvshows) + { + // TODO refactor into separate class or something, this is copied from LibraryManager.GetConfiguredContentType + var configuredContentType = args.GetConfiguredContentType(); + if (configuredContentType != CollectionType.tvshows) + { + return new Series + { + Path = args.Path, + Name = seriesInfo.Name + }; + } + } + else if (collectionType is null) + { + if (args.ContainsFileSystemEntryByName("tvshow.nfo")) + { + if (args.Parent is not null && args.Parent.IsRoot) + { + // For now, return null, but if we want to allow this in the future then add some additional checks to guard against a misplaced tvshow.nfo + return null; + } + + return new Series + { + Path = args.Path, + Name = seriesInfo.Name + }; + } + + if (args.Parent is not null && args.Parent.IsRoot) + { + return null; + } + + if (IsSeriesFolder(args.Path, args.FileSystemChildren, false)) + { + return new Series + { + Path = args.Path, + Name = seriesInfo.Name + }; + } + } + } + + return null; + } + + private bool IsSeriesFolder( + string path, + IEnumerable<FileSystemMetadata> fileSystemChildren, + bool isTvContentType) + { + foreach (var child in fileSystemChildren) + { + if (child.IsDirectory) + { + if (IsSeasonFolder(child.FullName, path, isTvContentType)) + { + _logger.LogDebug("{Path} is a series because of season folder {Dir}.", path, child.FullName); + return true; + } + } + else + { + string fullName = child.FullName; + if (VideoResolver.IsVideoFile(path, _namingOptions)) + { + if (isTvContentType) + { + return true; + } + + var namingOptions = _namingOptions; + + var episodeResolver = new Naming.TV.EpisodeResolver(namingOptions); + + var episodeInfo = episodeResolver.Resolve(fullName, false, true, false, fillExtendedInfo: false); + if (episodeInfo is not null && episodeInfo.EpisodeNumber.HasValue) + { + return true; + } + } + } + } + + _logger.LogDebug("{Path} is not a series folder.", path); + return false; + } + + /// <summary> + /// Determines whether [is season folder] [the specified path]. + /// </summary> + /// <param name="path">The path.</param> + /// <param name="parentPath">The parentpath.</param> + /// <param name="isTvContentType">if set to <c>true</c> [is tv content type].</param> + /// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns> + private static bool IsSeasonFolder(string path, string parentPath, bool isTvContentType) + { + var seasonNumber = SeasonPathParser.Parse(path, parentPath, isTvContentType, isTvContentType).SeasonNumber; + + return seasonNumber.HasValue; + } + + /// <summary> + /// Sets the initial item values. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="args">The args.</param> + protected override void SetInitialItemValues(Series item, ItemResolveArgs args) + { + base.SetInitialItemValues(item, args); + + SetProviderIdFromPath(item, args.Path); + } + + /// <summary> + /// Sets the provider id from path. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="path">The path.</param> + private static void SetProviderIdFromPath(Series item, string path) + { + var justName = Path.GetFileName(path.AsSpan()); + + var imdbId = justName.GetAttributeValue("imdbid"); + item.TrySetProviderId(MetadataProvider.Imdb, imdbId); + + var tvdbId = justName.GetAttributeValue("tvdbid"); + item.TrySetProviderId(MetadataProvider.Tvdb, tvdbId); + + var tvmazeId = justName.GetAttributeValue("tvmazeid"); + item.TrySetProviderId(MetadataProvider.TvMaze, tvmazeId); + + var tmdbId = justName.GetAttributeValue("tmdbid"); + item.TrySetProviderId(MetadataProvider.Tmdb, tmdbId); + + var anidbId = justName.GetAttributeValue("anidbid"); + item.TrySetProviderId("AniDB", anidbId); + + var aniListId = justName.GetAttributeValue("anilistid"); + item.TrySetProviderId("AniList", aniListId); + + var aniSearchId = justName.GetAttributeValue("anisearchid"); + item.TrySetProviderId("AniSearch", aniSearchId); + } + } +} diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs new file mode 100644 index 00000000..c6821185 --- /dev/null +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -0,0 +1,200 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Linq; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Querying; +using MediaBrowser.Model.Search; + +namespace Emby.Server.Implementations.Library +{ + public class SearchEngine : ISearchEngine + { + private readonly ILibraryManager _libraryManager; + private readonly IUserManager _userManager; + + public SearchEngine(ILibraryManager libraryManager, IUserManager userManager) + { + _libraryManager = libraryManager; + _userManager = userManager; + } + + public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query) + { + User? user = null; + if (!query.UserId.IsEmpty()) + { + user = _userManager.GetUserById(query.UserId); + } + + var results = GetSearchHints(query, user); + var totalRecordCount = results.Count; + + if (query.StartIndex.HasValue) + { + results = results.GetRange(query.StartIndex.Value, totalRecordCount - query.StartIndex.Value); + } + + if (query.Limit.HasValue && query.Limit.Value > 0) + { + results = results.GetRange(0, Math.Min(query.Limit.Value, results.Count)); + } + + return new QueryResult<SearchHintInfo>( + query.StartIndex, + totalRecordCount, + results); + } + + private static void AddIfMissing(List<BaseItemKind> list, BaseItemKind value) + { + if (!list.Contains(value)) + { + list.Add(value); + } + } + + /// <summary> + /// Gets the search hints. + /// </summary> + /// <param name="query">The query.</param> + /// <param name="user">The user.</param> + /// <returns>IEnumerable{SearchHintResult}.</returns> + /// <exception cref="ArgumentException"><c>query.SearchTerm</c> is <c>null</c> or empty.</exception> + private List<SearchHintInfo> GetSearchHints(SearchQuery query, User? user) + { + var searchTerm = query.SearchTerm; + + ArgumentException.ThrowIfNullOrEmpty(searchTerm); + + searchTerm = searchTerm.Trim().RemoveDiacritics(); + + var excludeItemTypes = query.ExcludeItemTypes.ToList(); + var includeItemTypes = query.IncludeItemTypes.ToList(); + + excludeItemTypes.Add(BaseItemKind.Year); + excludeItemTypes.Add(BaseItemKind.Folder); + + if (query.IncludeGenres && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.Genre))) + { + if (!query.IncludeMedia) + { + AddIfMissing(includeItemTypes, BaseItemKind.Genre); + AddIfMissing(includeItemTypes, BaseItemKind.MusicGenre); + } + } + else + { + AddIfMissing(excludeItemTypes, BaseItemKind.Genre); + AddIfMissing(excludeItemTypes, BaseItemKind.MusicGenre); + } + + if (query.IncludePeople && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.Person))) + { + if (!query.IncludeMedia) + { + AddIfMissing(includeItemTypes, BaseItemKind.Person); + } + } + else + { + AddIfMissing(excludeItemTypes, BaseItemKind.Person); + } + + if (query.IncludeStudios && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.Studio))) + { + if (!query.IncludeMedia) + { + AddIfMissing(includeItemTypes, BaseItemKind.Studio); + } + } + else + { + AddIfMissing(excludeItemTypes, BaseItemKind.Studio); + } + + if (query.IncludeArtists && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.MusicArtist))) + { + if (!query.IncludeMedia) + { + AddIfMissing(includeItemTypes, BaseItemKind.MusicArtist); + } + } + else + { + AddIfMissing(excludeItemTypes, BaseItemKind.MusicArtist); + } + + AddIfMissing(excludeItemTypes, BaseItemKind.CollectionFolder); + AddIfMissing(excludeItemTypes, BaseItemKind.Folder); + var mediaTypes = query.MediaTypes.ToList(); + + if (includeItemTypes.Count > 0) + { + excludeItemTypes.Clear(); + mediaTypes.Clear(); + } + + var searchQuery = new InternalItemsQuery(user) + { + SearchTerm = searchTerm, + ExcludeItemTypes = excludeItemTypes.ToArray(), + IncludeItemTypes = includeItemTypes.ToArray(), + Limit = query.Limit, + IncludeItemsByName = !query.ParentId.HasValue, + ParentId = query.ParentId ?? Guid.Empty, + OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }, + Recursive = true, + + IsKids = query.IsKids, + IsMovie = query.IsMovie, + IsNews = query.IsNews, + IsSeries = query.IsSeries, + IsSports = query.IsSports, + MediaTypes = mediaTypes.ToArray(), + + DtoOptions = new DtoOptions + { + Fields = new ItemFields[] + { + ItemFields.AirTime, + ItemFields.DateCreated, + ItemFields.ChannelInfo, + ItemFields.ParentId + } + } + }; + + IReadOnlyList<BaseItem> mediaItems; + + if (searchQuery.IncludeItemTypes.Length == 1 && searchQuery.IncludeItemTypes[0] == BaseItemKind.MusicArtist) + { + if (!searchQuery.ParentId.IsEmpty()) + { + searchQuery.AncestorIds = [searchQuery.ParentId]; + searchQuery.ParentId = Guid.Empty; + } + + searchQuery.IncludeItemsByName = true; + searchQuery.IncludeItemTypes = Array.Empty<BaseItemKind>(); + mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item).ToList(); + } + else + { + mediaItems = _libraryManager.GetItemList(searchQuery); + } + + return mediaItems.Select(i => new SearchHintInfo + { + Item = i + }).ToList(); + } + } +} diff --git a/Emby.Server.Implementations/Library/SplashscreenPostScanTask.cs b/Emby.Server.Implementations/Library/SplashscreenPostScanTask.cs new file mode 100644 index 00000000..71ce3b60 --- /dev/null +++ b/Emby.Server.Implementations/Library/SplashscreenPostScanTask.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library; + +/// <summary> +/// The splashscreen post scan task. +/// </summary> +public class SplashscreenPostScanTask : ILibraryPostScanTask +{ + private readonly IItemRepository _itemRepository; + private readonly IImageEncoder _imageEncoder; + private readonly ILogger<SplashscreenPostScanTask> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="SplashscreenPostScanTask"/> class. + /// </summary> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + /// <param name="imageEncoder">Instance of the <see cref="IImageEncoder"/> interface.</param> + /// <param name="logger">Instance of the <see cref="ILogger{SplashscreenPostScanTask}"/> interface.</param> + public SplashscreenPostScanTask( + IItemRepository itemRepository, + IImageEncoder imageEncoder, + ILogger<SplashscreenPostScanTask> logger) + { + _itemRepository = itemRepository; + _imageEncoder = imageEncoder; + _logger = logger; + } + + /// <inheritdoc /> + public Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var posters = GetItemsWithImageType(ImageType.Primary) + .Select(x => x.GetImages(ImageType.Primary).FirstOrDefault()?.Path) + .Where(path => !string.IsNullOrEmpty(path)) + .Select(path => path!) + .ToList(); + var backdrops = GetItemsWithImageType(ImageType.Thumb) + .Select(x => x.GetImages(ImageType.Thumb).FirstOrDefault()?.Path) + .Where(path => !string.IsNullOrEmpty(path)) + .Select(path => path!) + .ToList(); + if (backdrops.Count == 0) + { + // Thumb images fit better because they include the title in the image but are not provided with TMDb. + // Using backdrops as a fallback to generate an image at all + _logger.LogDebug("No thumb images found. Using backdrops to generate splashscreen"); + backdrops = GetItemsWithImageType(ImageType.Backdrop) + .Select(x => x.GetImages(ImageType.Backdrop).FirstOrDefault()?.Path) + .Where(path => !string.IsNullOrEmpty(path)) + .Select(path => path!) + .ToList(); + } + + _imageEncoder.CreateSplashscreen(posters, backdrops); + return Task.CompletedTask; + } + + private IReadOnlyList<BaseItem> GetItemsWithImageType(ImageType imageType) + { + // TODO make included libraries configurable + return _itemRepository.GetItemList(new InternalItemsQuery + { + CollapseBoxSetItems = false, + Recursive = true, + DtoOptions = new DtoOptions(false), + ImageTypes = [imageType], + Limit = 30, + // TODO max parental rating configurable + MaxParentalRating = new(10, null), + OrderBy = + [ + (ItemSortBy.Random, SortOrder.Ascending) + ], + IncludeItemTypes = [BaseItemKind.Movie, BaseItemKind.Series] + }); + } +} diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs new file mode 100644 index 00000000..72c8d7a9 --- /dev/null +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -0,0 +1,368 @@ +#pragma warning disable RS0030 // Do not use banned APIs + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using BitFaster.Caching.Lru; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using Microsoft.EntityFrameworkCore; +using AudioBook = MediaBrowser.Controller.Entities.AudioBook; +using Book = MediaBrowser.Controller.Entities.Book; + +namespace Emby.Server.Implementations.Library +{ + /// <summary> + /// Class UserDataManager. + /// </summary> + public class UserDataManager : IUserDataManager + { + private readonly IServerConfigurationManager _config; + private readonly IDbContextFactory<JellyfinDbContext> _repository; + private readonly FastConcurrentLru<string, UserItemData> _cache; + + /// <summary> + /// Initializes a new instance of the <see cref="UserDataManager"/> class. + /// </summary> + /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="repository">Instance of the <see cref="IDbContextFactory{JellyfinDbContext}"/> interface.</param> + public UserDataManager( + IServerConfigurationManager config, + IDbContextFactory<JellyfinDbContext> repository) + { + _config = config; + _repository = repository; + _cache = new FastConcurrentLru<string, UserItemData>(Environment.ProcessorCount, _config.Configuration.CacheSize, StringComparer.OrdinalIgnoreCase); + } + + /// <inheritdoc /> + public event EventHandler<UserDataSaveEventArgs>? UserDataSaved; + + /// <inheritdoc /> + public void SaveUserData(User user, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(userData); + + ArgumentNullException.ThrowIfNull(item); + + cancellationToken.ThrowIfCancellationRequested(); + + var keys = item.GetUserDataKeys(); + + using var dbContext = _repository.CreateDbContext(); + using var transaction = dbContext.Database.BeginTransaction(); + + foreach (var key in keys) + { + userData.Key = key; + var userDataEntry = Map(userData, user.Id, item.Id); + if (dbContext.UserData.Any(f => f.ItemId == userDataEntry.ItemId && f.UserId == userDataEntry.UserId && f.CustomDataKey == userDataEntry.CustomDataKey)) + { + dbContext.UserData.Attach(userDataEntry).State = EntityState.Modified; + } + else + { + dbContext.UserData.Add(userDataEntry); + } + } + + dbContext.SaveChanges(); + transaction.Commit(); + + var userId = user.InternalId; + var cacheKey = GetCacheKey(userId, item.Id); + _cache.AddOrUpdate(cacheKey, userData); + item.UserData = dbContext.UserData.Where(e => e.ItemId == item.Id).AsNoTracking().ToArray(); // rehydrate the cached userdata + + UserDataSaved?.Invoke(this, new UserDataSaveEventArgs + { + Keys = keys, + UserData = userData, + SaveReason = reason, + UserId = user.Id, + Item = item + }); + } + + /// <inheritdoc /> + public void SaveUserData(User user, BaseItem item, UpdateUserItemDataDto userDataDto, UserDataSaveReason reason) + { + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(item); + ArgumentNullException.ThrowIfNull(userDataDto); + + var userData = GetUserData(user, item) ?? throw new InvalidOperationException("UserData should not be null."); + + if (userDataDto.PlaybackPositionTicks.HasValue) + { + userData.PlaybackPositionTicks = userDataDto.PlaybackPositionTicks.Value; + } + + if (userDataDto.PlayCount.HasValue) + { + userData.PlayCount = userDataDto.PlayCount.Value; + } + + if (userDataDto.IsFavorite.HasValue) + { + userData.IsFavorite = userDataDto.IsFavorite.Value; + } + + if (userDataDto.Likes.HasValue) + { + userData.Likes = userDataDto.Likes.Value; + } + + if (userDataDto.Played.HasValue) + { + userData.Played = userDataDto.Played.Value; + } + + if (userDataDto.LastPlayedDate.HasValue) + { + userData.LastPlayedDate = userDataDto.LastPlayedDate.Value; + } + + if (userDataDto.Rating.HasValue) + { + userData.Rating = userDataDto.Rating.Value; + } + + SaveUserData(user, item, userData, reason, CancellationToken.None); + } + + private UserData Map(UserItemData dto, Guid userId, Guid itemId) + { + return new UserData() + { + ItemId = itemId, + CustomDataKey = dto.Key, + Item = null, + User = null, + AudioStreamIndex = dto.AudioStreamIndex, + IsFavorite = dto.IsFavorite, + LastPlayedDate = dto.LastPlayedDate, + Likes = dto.Likes, + PlaybackPositionTicks = dto.PlaybackPositionTicks, + PlayCount = dto.PlayCount, + Played = dto.Played, + Rating = dto.Rating, + UserId = userId, + SubtitleStreamIndex = dto.SubtitleStreamIndex, + }; + } + + private static UserItemData Map(UserData dto) + { + return new UserItemData() + { + Key = dto.CustomDataKey!, + AudioStreamIndex = dto.AudioStreamIndex, + IsFavorite = dto.IsFavorite, + LastPlayedDate = dto.LastPlayedDate, + Likes = dto.Likes, + PlaybackPositionTicks = dto.PlaybackPositionTicks, + PlayCount = dto.PlayCount, + Played = dto.Played, + Rating = dto.Rating, + SubtitleStreamIndex = dto.SubtitleStreamIndex, + }; + } + + private UserItemData? GetUserData(User user, Guid itemId, List<string> keys) + { + var cacheKey = GetCacheKey(user.InternalId, itemId); + + if (_cache.TryGet(cacheKey, out var data)) + { + return data; + } + + data = GetUserDataInternal(user.Id, itemId, keys); + + if (data is null) + { + return new UserItemData() + { + Key = keys[0], + }; + } + + return _cache.GetOrAdd(cacheKey, _ => data); + } + + private UserItemData? GetUserDataInternal(Guid userId, Guid itemId, List<string> keys) + { + if (keys.Count == 0) + { + return null; + } + + using var context = _repository.CreateDbContext(); + var userData = context.UserData.AsNoTracking().Where(e => e.ItemId == itemId && keys.Contains(e.CustomDataKey) && e.UserId.Equals(userId)).ToArray(); + + if (userData.Length > 0) + { + var directDataReference = userData.FirstOrDefault(e => e.CustomDataKey == itemId.ToString("N")); + if (directDataReference is not null) + { + return Map(directDataReference); + } + + return Map(userData.First()); + } + + return new UserItemData + { + Key = keys.Last()! + }; + } + + /// <summary> + /// Gets the internal key. + /// </summary> + /// <returns>System.String.</returns> + private static string GetCacheKey(long internalUserId, Guid itemId) + { + return internalUserId.ToString(CultureInfo.InvariantCulture) + "-" + itemId.ToString("N", CultureInfo.InvariantCulture); + } + + /// <inheritdoc /> + public UserItemData? GetUserData(User user, BaseItem item) + { + return item.UserData?.Where(e => e.UserId.Equals(user.Id)).Select(Map).FirstOrDefault() ?? new UserItemData() + { + Key = item.GetUserDataKeys()[0], + }; + } + + /// <inheritdoc /> + public UserItemDataDto? GetUserDataDto(BaseItem item, User user) + => GetUserDataDto(item, null, user, new DtoOptions()); + + /// <inheritdoc /> + public UserItemDataDto? GetUserDataDto(BaseItem item, BaseItemDto? itemDto, User user, DtoOptions options) + { + var userData = GetUserData(user, item); + if (userData is null) + { + return null; + } + + var dto = GetUserItemDataDto(userData, item.Id); + + item.FillUserDataDtoValues(dto, userData, itemDto, user, options); + return dto; + } + + /// <summary> + /// Converts a UserItemData to a DTOUserItemData. + /// </summary> + /// <param name="data">The data.</param> + /// <param name="itemId">The reference key to an Item.</param> + /// <returns>DtoUserItemData.</returns> + /// <exception cref="ArgumentNullException"><paramref name="data"/> is <c>null</c>.</exception> + private UserItemDataDto GetUserItemDataDto(UserItemData data, Guid itemId) + { + ArgumentNullException.ThrowIfNull(data); + + return new UserItemDataDto + { + IsFavorite = data.IsFavorite, + Likes = data.Likes, + PlaybackPositionTicks = data.PlaybackPositionTicks, + PlayCount = data.PlayCount, + Rating = data.Rating, + Played = data.Played, + LastPlayedDate = data.LastPlayedDate, + ItemId = itemId, + Key = data.Key + }; + } + + /// <inheritdoc /> + public bool UpdatePlayState(BaseItem item, UserItemData data, long? reportedPositionTicks) + { + var playedToCompletion = false; + + var runtimeTicks = item.GetRunTimeTicksForPlayState(); + + var positionTicks = reportedPositionTicks ?? runtimeTicks; + var hasRuntime = runtimeTicks > 0; + + // If a position has been reported, and if we know the duration + if (positionTicks > 0 && hasRuntime && item is not AudioBook && item is not Book) + { + var pctIn = decimal.Divide(positionTicks, runtimeTicks) * 100; + + if (pctIn < _config.Configuration.MinResumePct) + { + // ignore progress during the beginning + positionTicks = 0; + } + else if (pctIn > _config.Configuration.MaxResumePct || positionTicks >= (runtimeTicks - TimeSpan.TicksPerSecond)) + { + // mark as completed close to the end + positionTicks = 0; + data.Played = playedToCompletion = true; + } + else + { + // Enforce MinResumeDuration + var durationSeconds = TimeSpan.FromTicks(runtimeTicks).TotalSeconds; + if (durationSeconds < _config.Configuration.MinResumeDurationSeconds) + { + positionTicks = 0; + data.Played = playedToCompletion = true; + } + } + } + else if (positionTicks > 0 && hasRuntime && item is AudioBook) + { + var playbackPositionInMinutes = TimeSpan.FromTicks(positionTicks).TotalMinutes; + var remainingTimeInMinutes = TimeSpan.FromTicks(runtimeTicks - positionTicks).TotalMinutes; + + if (playbackPositionInMinutes < _config.Configuration.MinAudiobookResume) + { + // ignore progress during the beginning + positionTicks = 0; + } + else if (remainingTimeInMinutes < _config.Configuration.MaxAudiobookResume || positionTicks >= runtimeTicks) + { + // mark as completed close to the end + positionTicks = 0; + data.Played = playedToCompletion = true; + } + } + else if (!hasRuntime) + { + // If we don't know the runtime we'll just have to assume it was fully played + data.Played = playedToCompletion = true; + positionTicks = 0; + } + + if (!item.SupportsPlayedStatus) + { + positionTicks = 0; + data.Played = false; + } + + if (!item.SupportsPositionTicksResume) + { + positionTicks = 0; + } + + data.PlaybackPositionTicks = positionTicks; + + return playedToCompletion; + } + } +} diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs new file mode 100644 index 00000000..6fb53ff1 --- /dev/null +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -0,0 +1,402 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Jellyfin.Data; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.Channels; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Library; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Library +{ + public class UserViewManager : IUserViewManager + { + private readonly ILibraryManager _libraryManager; + private readonly ILocalizationManager _localizationManager; + + private readonly IChannelManager _channelManager; + private readonly ILiveTvManager _liveTvManager; + private readonly IServerConfigurationManager _config; + + public UserViewManager(ILibraryManager libraryManager, ILocalizationManager localizationManager, IChannelManager channelManager, ILiveTvManager liveTvManager, IServerConfigurationManager config) + { + _libraryManager = libraryManager; + _localizationManager = localizationManager; + _channelManager = channelManager; + _liveTvManager = liveTvManager; + _config = config; + } + + public Folder[] GetUserViews(UserViewQuery query) + { + var user = query.User; + + var folders = _libraryManager.GetUserRootFolder() + .GetChildren(user, true) + .OfType<Folder>() + .ToList(); + + var groupedFolders = new List<ICollectionFolder>(); + var list = new List<Folder>(); + + foreach (var folder in folders) + { + var collectionFolder = folder as ICollectionFolder; + var folderViewType = collectionFolder?.CollectionType; + + // Playlist library requires special handling because the folder only references user playlists + if (folderViewType == CollectionType.playlists) + { + var items = folder.GetItemList(new InternalItemsQuery(user) + { + ParentId = folder.ParentId + }); + + if (!items.Any(item => item.IsVisible(user))) + { + continue; + } + } + + if (UserView.IsUserSpecific(folder)) + { + list.Add(_libraryManager.GetNamedView(user, folder.Name, folder.Id, folderViewType, null)); + continue; + } + + if (collectionFolder is not null && UserView.IsEligibleForGrouping(folder) && user.IsFolderGrouped(folder.Id)) + { + groupedFolders.Add(collectionFolder); + continue; + } + + if (query.PresetViews.Contains(folderViewType)) + { + list.Add(GetUserView(folder, folderViewType, string.Empty)); + } + else + { + list.Add(folder); + } + } + + foreach (var viewType in new[] { CollectionType.movies, CollectionType.tvshows }) + { + var parents = groupedFolders.Where(i => i.CollectionType == viewType || i.CollectionType is null) + .ToList(); + + if (parents.Count > 0) + { + var localizationKey = viewType == CollectionType.tvshows + ? "TvShows" + : "Movies"; + + list.Add(GetUserView(parents, viewType, localizationKey, string.Empty, user, query.PresetViews)); + } + } + + if (_config.Configuration.EnableFolderView) + { + var name = _localizationManager.GetLocalizedString("Folders"); + list.Add(_libraryManager.GetNamedView(name, CollectionType.folders, string.Empty)); + } + + if (query.IncludeExternalContent) + { + var channelResult = _channelManager.GetChannelsInternalAsync(new ChannelQuery + { + UserId = user.Id + }).GetAwaiter().GetResult(); + + var channels = channelResult.Items; + + list.AddRange(channels); + + if (_liveTvManager.GetEnabledUsers().Select(i => i.Id).Contains(user.Id)) + { + list.Add(_liveTvManager.GetInternalLiveTvFolder(CancellationToken.None)); + } + } + + if (!query.IncludeHidden) + { + list = list.Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.MyMediaExcludes).Contains(i.Id)).ToList(); + } + + var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList(); + var orders = user.GetPreferenceValues<Guid>(PreferenceKind.OrderedViews); + + return list + .OrderBy(i => + { + var index = Array.IndexOf(orders, i.Id); + if (index == -1 + && i is UserView view + && !view.DisplayParentId.IsEmpty()) + { + index = Array.IndexOf(orders, view.DisplayParentId); + } + + return index == -1 ? int.MaxValue : index; + }) + .ThenBy(sorted.IndexOf) + .ThenBy(i => i.SortName) + .ToArray(); + } + + public UserView GetUserSubViewWithName(string name, Guid parentId, CollectionType? type, string sortName) + { + var uniqueId = parentId + "subview" + type; + + return _libraryManager.GetNamedView(name, parentId, type, sortName, uniqueId); + } + + public UserView GetUserSubView(Guid parentId, CollectionType? type, string localizationKey, string sortName) + { + var name = _localizationManager.GetLocalizedString(localizationKey); + + return GetUserSubViewWithName(name, parentId, type, sortName); + } + + private Folder GetUserView( + List<ICollectionFolder> parents, + CollectionType? viewType, + string localizationKey, + string sortName, + User user, + CollectionType?[] presetViews) + { + if (parents.Count == 1 && parents.All(i => i.CollectionType == viewType)) + { + if (!presetViews.Contains(viewType)) + { + return (Folder)parents[0]; + } + + return GetUserView((Folder)parents[0], viewType, string.Empty); + } + + var name = _localizationManager.GetLocalizedString(localizationKey); + return _libraryManager.GetNamedView(user, name, viewType, sortName); + } + + public UserView GetUserView(Folder parent, CollectionType? viewType, string sortName) + { + return _libraryManager.GetShadowView(parent, viewType, sortName); + } + + public List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request, DtoOptions options) + { + var libraryItems = GetItemsForLatestItems(request.User, request, options); + + var list = new List<Tuple<BaseItem, List<BaseItem>>>(); + + foreach (var item in libraryItems) + { + // Only grab the index container for media + var container = item.IsFolder || !request.GroupItems ? null : item.LatestItemsIndexContainer; + + if (container is null) + { + list.Add(new Tuple<BaseItem, List<BaseItem>>(null, new List<BaseItem> { item })); + } + else + { + var current = list.FirstOrDefault(i => i.Item1 is not null && i.Item1.Id.Equals(container.Id)); + + if (current is not null) + { + current.Item2.Add(item); + } + else + { + list.Add(new Tuple<BaseItem, List<BaseItem>>(container, new List<BaseItem> { item })); + } + } + + if (list.Count >= request.Limit) + { + break; + } + } + + return list; + } + + private IReadOnlyList<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request, DtoOptions options) + { + var parentId = request.ParentId; + + var includeItemTypes = request.IncludeItemTypes; + var limit = request.Limit ?? 10; + + var parents = new List<BaseItem>(); + + if (!parentId.IsEmpty()) + { + var parentItem = _libraryManager.GetItemById(parentId); + if (parentItem is Channel) + { + return _channelManager.GetLatestChannelItemsInternal( + new InternalItemsQuery(user) + { + ChannelIds = new[] { parentId }, + IsPlayed = request.IsPlayed, + StartIndex = request.StartIndex, + Limit = request.Limit, + IncludeItemTypes = request.IncludeItemTypes, + EnableTotalRecordCount = false + }, + CancellationToken.None).GetAwaiter().GetResult().Items; + } + + if (parentItem is Folder parent) + { + parents.Add(parent); + } + } + + var isPlayed = request.IsPlayed; + + if (parents.OfType<ICollectionFolder>().Any(i => i.CollectionType == CollectionType.music)) + { + isPlayed = null; + } + + if (parents.Count == 0) + { + parents = _libraryManager.GetUserRootFolder().GetChildren(user, true) + .Where(i => i is Folder) + .Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes) + .Contains(i.Id)) + .ToList(); + } + + if (parents.Count == 0) + { + return Array.Empty<BaseItem>(); + } + + if (includeItemTypes.Length == 0) + { + // Handle situations with the grouping setting, e.g. movies showing up in tv, etc. + // Thanks to mixed content libraries included in the UserView + var hasCollectionType = parents.OfType<UserView>().ToList(); + if (hasCollectionType.Count > 0) + { + if (hasCollectionType.All(i => i.CollectionType == CollectionType.movies)) + { + includeItemTypes = new[] { BaseItemKind.Movie }; + } + else if (hasCollectionType.All(i => i.CollectionType == CollectionType.tvshows)) + { + includeItemTypes = new[] { BaseItemKind.Episode }; + } + } + } + + MediaType[] mediaTypes = []; + + if (includeItemTypes.Length == 0) + { + HashSet<MediaType> tmpMediaTypes = []; + foreach (var parent in parents.OfType<ICollectionFolder>()) + { + switch (parent.CollectionType) + { + case CollectionType.books: + tmpMediaTypes.Add(MediaType.Book); + tmpMediaTypes.Add(MediaType.Audio); + break; + case CollectionType.music: + tmpMediaTypes.Add(MediaType.Audio); + break; + case CollectionType.photos: + tmpMediaTypes.Add(MediaType.Photo); + tmpMediaTypes.Add(MediaType.Video); + break; + case CollectionType.homevideos: + tmpMediaTypes.Add(MediaType.Photo); + tmpMediaTypes.Add(MediaType.Video); + break; + default: + tmpMediaTypes.Add(MediaType.Video); + break; + } + } + + mediaTypes = tmpMediaTypes.ToArray(); + } + + var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Length == 0 + ? new[] + { + BaseItemKind.Person, + BaseItemKind.Studio, + BaseItemKind.Year, + BaseItemKind.MusicGenre, + BaseItemKind.Genre + } + : Array.Empty<BaseItemKind>(); + + var query = new InternalItemsQuery(user) + { + IncludeItemTypes = includeItemTypes, + OrderBy = new[] + { + (ItemSortBy.DateCreated, SortOrder.Descending), + (ItemSortBy.SortName, SortOrder.Descending), + (ItemSortBy.ProductionYear, SortOrder.Descending) + }, + IsFolder = includeItemTypes.Length == 0 ? false : null, + ExcludeItemTypes = excludeItemTypes, + IsVirtualItem = false, + Limit = limit * 5, + IsPlayed = isPlayed, + DtoOptions = options, + MediaTypes = mediaTypes + }; + + if (request.GroupItems) + { + var collectionType = parents + .Select(parent => parent switch + { + ICollectionFolder collectionFolder => collectionFolder.CollectionType, + UserView userView => userView.CollectionType, + _ => null + }) + .FirstOrDefault(type => type is not null); + + if (collectionType == CollectionType.tvshows) + { + query.Limit = limit; + return _libraryManager.GetLatestItemList(query, parents, CollectionType.tvshows); + } + + if (collectionType == CollectionType.music) + { + query.Limit = limit; + return _libraryManager.GetLatestItemList(query, parents, CollectionType.music); + } + } + + return _libraryManager.GetItemList(query, parents); + } + } +} diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs new file mode 100644 index 00000000..a31d5ecc --- /dev/null +++ b/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class ArtistsPostScanTask. +/// </summary> +public class ArtistsPostScanTask : ILibraryPostScanTask +{ + /// <summary> + /// The _library manager. + /// </summary> + private readonly ILibraryManager _libraryManager; + private readonly ILogger<ArtistsValidator> _logger; + private readonly IItemRepository _itemRepo; + + /// <summary> + /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> + public ArtistsPostScanTask( + ILibraryManager libraryManager, + ILogger<ArtistsValidator> logger, + IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _itemRepo = itemRepo; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + return new ArtistsValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); + } +} diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs new file mode 100644 index 00000000..7cc851b7 --- /dev/null +++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -0,0 +1,110 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class ArtistsValidator. +/// </summary> +public class ArtistsValidator +{ + /// <summary> + /// The library manager. + /// </summary> + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// The logger. + /// </summary> + private readonly ILogger<ArtistsValidator> _logger; + private readonly IItemRepository _itemRepo; + + /// <summary> + /// Initializes a new instance of the <see cref="ArtistsValidator" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> + public ArtistsValidator(ILibraryManager libraryManager, ILogger<ArtistsValidator> logger, IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _itemRepo = itemRepo; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var names = _itemRepo.GetAllArtistNames(); + + var numComplete = 0; + var count = names.Count; + + foreach (var name in names) + { + try + { + var item = _libraryManager.GetArtist(name); + + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // Don't clutter the log + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error refreshing {ArtistName}", name); + } + + numComplete++; + double percent = numComplete; + percent /= count; + percent *= 100; + + progress.Report(percent); + } + + var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = [BaseItemKind.MusicArtist], + IsDeadArtist = true, + IsLocked = false + }).Cast<MusicArtist>().ToList(); + + foreach (var item in deadEntities) + { + if (!item.IsAccessedByName) + { + continue; + } + + _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name); + + _libraryManager.DeleteItem( + item, + new DeleteOptions + { + DeleteFileLocation = false + }, + false); + } + + progress.Report(100); + } +} diff --git a/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs new file mode 100644 index 00000000..e62c638e --- /dev/null +++ b/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Controller.Collections; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class CollectionPostScanTask. +/// </summary> +public class CollectionPostScanTask : ILibraryPostScanTask +{ + private readonly ILibraryManager _libraryManager; + private readonly ICollectionManager _collectionManager; + private readonly ILogger<CollectionPostScanTask> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="CollectionPostScanTask" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="collectionManager">The collection manager.</param> + /// <param name="logger">The logger.</param> + public CollectionPostScanTask( + ILibraryManager libraryManager, + ICollectionManager collectionManager, + ILogger<CollectionPostScanTask> logger) + { + _libraryManager = libraryManager; + _collectionManager = collectionManager; + _logger = logger; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var collectionNameMoviesMap = new Dictionary<string, HashSet<Guid>>(); + + foreach (var library in _libraryManager.RootFolder.Children) + { + if (!_libraryManager.GetLibraryOptions(library).AutomaticallyAddToCollection) + { + continue; + } + + var startIndex = 0; + var pagesize = 1000; + + while (true) + { + var movies = _libraryManager.GetItemList(new InternalItemsQuery + { + MediaTypes = [MediaType.Video], + IncludeItemTypes = [BaseItemKind.Movie], + IsVirtualItem = false, + OrderBy = [(ItemSortBy.SortName, SortOrder.Ascending)], + Parent = library, + StartIndex = startIndex, + Limit = pagesize, + Recursive = true + }); + + foreach (var m in movies) + { + if (m is Movie movie && !string.IsNullOrEmpty(movie.CollectionName)) + { + if (collectionNameMoviesMap.TryGetValue(movie.CollectionName, out var movieList)) + { + movieList.Add(movie.Id); + } + else + { + collectionNameMoviesMap[movie.CollectionName] = new HashSet<Guid> { movie.Id }; + } + } + } + + if (movies.Count < pagesize) + { + break; + } + + startIndex += pagesize; + } + } + + var numComplete = 0; + var count = collectionNameMoviesMap.Count; + + if (count == 0) + { + progress.Report(100); + return; + } + + var boxSets = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = [BaseItemKind.BoxSet], + CollapseBoxSetItems = false, + Recursive = true + }); + + foreach (var (collectionName, movieIds) in collectionNameMoviesMap) + { + try + { + var boxSet = boxSets.FirstOrDefault(b => b?.Name == collectionName) as BoxSet; + if (boxSet is null) + { + // won't automatically create collection if only one movie in it + if (movieIds.Count >= 2) + { + boxSet = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions + { + Name = collectionName, + }).ConfigureAwait(false); + + await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds).ConfigureAwait(false); + } + } + else + { + await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds).ConfigureAwait(false); + } + + numComplete++; + double percent = numComplete; + percent /= count; + percent *= 100; + + progress.Report(percent); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error refreshing {CollectionName} with {@MovieIds}", collectionName, movieIds); + } + } + + progress.Report(100); + } +} diff --git a/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs new file mode 100644 index 00000000..5097e007 --- /dev/null +++ b/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class GenresPostScanTask. +/// </summary> +public class GenresPostScanTask : ILibraryPostScanTask +{ + /// <summary> + /// The _library manager. + /// </summary> + private readonly ILibraryManager _libraryManager; + private readonly ILogger<GenresValidator> _logger; + private readonly IItemRepository _itemRepo; + + /// <summary> + /// Initializes a new instance of the <see cref="GenresPostScanTask" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> + public GenresPostScanTask( + ILibraryManager libraryManager, + ILogger<GenresValidator> logger, + IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _itemRepo = itemRepo; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + return new GenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); + } +} diff --git a/Emby.Server.Implementations/Library/Validators/GenresValidator.cs b/Emby.Server.Implementations/Library/Validators/GenresValidator.cs new file mode 100644 index 00000000..fbfc9f7d --- /dev/null +++ b/Emby.Server.Implementations/Library/Validators/GenresValidator.cs @@ -0,0 +1,103 @@ +using System; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class GenresValidator. +/// </summary> +public class GenresValidator +{ + /// <summary> + /// The library manager. + /// </summary> + private readonly ILibraryManager _libraryManager; + private readonly IItemRepository _itemRepo; + + /// <summary> + /// The logger. + /// </summary> + private readonly ILogger<GenresValidator> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="GenresValidator"/> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> + public GenresValidator(ILibraryManager libraryManager, ILogger<GenresValidator> logger, IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _itemRepo = itemRepo; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var names = _itemRepo.GetGenreNames(); + + var numComplete = 0; + var count = names.Count; + + foreach (var name in names) + { + try + { + var item = _libraryManager.GetGenre(name); + + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // Don't clutter the log + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error refreshing {GenreName}", name); + } + + numComplete++; + double percent = numComplete; + percent /= count; + percent *= 100; + + progress.Report(percent); + } + + var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = [BaseItemKind.Genre, BaseItemKind.MusicGenre], + IsDeadGenre = true, + IsLocked = false + }); + + foreach (var item in deadEntities) + { + _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name); + + _libraryManager.DeleteItem( + item, + new DeleteOptions + { + DeleteFileLocation = false + }, + false); + } + + progress.Report(100); + } +} diff --git a/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs new file mode 100644 index 00000000..76658a81 --- /dev/null +++ b/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class MusicGenresPostScanTask. +/// </summary> +public class MusicGenresPostScanTask : ILibraryPostScanTask +{ + /// <summary> + /// The library manager. + /// </summary> + private readonly ILibraryManager _libraryManager; + private readonly ILogger<MusicGenresValidator> _logger; + private readonly IItemRepository _itemRepo; + + /// <summary> + /// Initializes a new instance of the <see cref="MusicGenresPostScanTask" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> + public MusicGenresPostScanTask( + ILibraryManager libraryManager, + ILogger<MusicGenresValidator> logger, + IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _itemRepo = itemRepo; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + return new MusicGenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); + } +} diff --git a/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs b/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs new file mode 100644 index 00000000..6203bce2 --- /dev/null +++ b/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs @@ -0,0 +1,80 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class MusicGenresValidator. +/// </summary> +public class MusicGenresValidator +{ + /// <summary> + /// The library manager. + /// </summary> + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// The logger. + /// </summary> + private readonly ILogger<MusicGenresValidator> _logger; + private readonly IItemRepository _itemRepo; + + /// <summary> + /// Initializes a new instance of the <see cref="MusicGenresValidator" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> + public MusicGenresValidator(ILibraryManager libraryManager, ILogger<MusicGenresValidator> logger, IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _itemRepo = itemRepo; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var names = _itemRepo.GetMusicGenreNames(); + + var numComplete = 0; + var count = names.Count; + + foreach (var name in names) + { + try + { + var item = _libraryManager.GetMusicGenre(name); + + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // Don't clutter the log + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error refreshing {GenreName}", name); + } + + numComplete++; + double percent = numComplete; + percent /= count; + percent *= 100; + + progress.Report(percent); + } + + progress.Report(100); + } +} diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs new file mode 100644 index 00000000..f9a6f0d1 --- /dev/null +++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -0,0 +1,120 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.IO; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class PeopleValidator. +/// </summary> +public class PeopleValidator +{ + /// <summary> + /// The _library manager. + /// </summary> + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// The _logger. + /// </summary> + private readonly ILogger _logger; + + private readonly IFileSystem _fileSystem; + + /// <summary> + /// Initializes a new instance of the <see cref="PeopleValidator" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="fileSystem">The file system.</param> + public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem) + { + _libraryManager = libraryManager; + _logger = logger; + _fileSystem = fileSystem; + } + + /// <summary> + /// Validates the people. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + public async Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress) + { + var people = _libraryManager.GetPeopleNames(new InternalPeopleQuery()); + + var numComplete = 0; + + var numPeople = people.Count; + + IProgress<double> subProgress = new Progress<double>((val) => progress.Report(val / 2)); + + _logger.LogDebug("Will refresh {Amount} people", numPeople); + + foreach (var person in people) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + var item = _libraryManager.GetPerson(person); + if (item is null) + { + _logger.LogWarning("Failed to get person: {Name}", person); + continue; + } + + var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + ImageRefreshMode = MetadataRefreshMode.ValidationOnly, + MetadataRefreshMode = MetadataRefreshMode.ValidationOnly + }; + + await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error validating IBN entry {Person}", person); + } + + // Update progress + numComplete++; + double percent = numComplete; + percent /= numPeople; + + subProgress.Report(100 * percent); + } + + var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = [BaseItemKind.Person], + IsDeadPerson = true, + IsLocked = false + }); + + subProgress = new Progress<double>((val) => progress.Report((val / 2) + 50)); + + var i = 0; + foreach (var item in deadEntities.Chunk(500)) + { + _libraryManager.DeleteItemsUnsafeFast(item); + subProgress.Report(100f / deadEntities.Count * (i++ * 100)); + } + + progress.Report(100); + + _logger.LogInformation("People validation complete"); + } +} diff --git a/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs new file mode 100644 index 00000000..67c56c10 --- /dev/null +++ b/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class MusicGenresPostScanTask. +/// </summary> +public class StudiosPostScanTask : ILibraryPostScanTask +{ + /// <summary> + /// The _library manager. + /// </summary> + private readonly ILibraryManager _libraryManager; + + private readonly ILogger<StudiosValidator> _logger; + private readonly IItemRepository _itemRepo; + + /// <summary> + /// Initializes a new instance of the <see cref="StudiosPostScanTask" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> + public StudiosPostScanTask( + ILibraryManager libraryManager, + ILogger<StudiosValidator> logger, + IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _itemRepo = itemRepo; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + return new StudiosValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); + } +} diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs new file mode 100644 index 00000000..5b87e4d9 --- /dev/null +++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -0,0 +1,104 @@ +using System; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class StudiosValidator. +/// </summary> +public class StudiosValidator +{ + /// <summary> + /// The library manager. + /// </summary> + private readonly ILibraryManager _libraryManager; + + private readonly IItemRepository _itemRepo; + + /// <summary> + /// The logger. + /// </summary> + private readonly ILogger<StudiosValidator> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="StudiosValidator" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> + public StudiosValidator(ILibraryManager libraryManager, ILogger<StudiosValidator> logger, IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _itemRepo = itemRepo; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var names = _itemRepo.GetStudioNames(); + + var numComplete = 0; + var count = names.Count; + + foreach (var name in names) + { + try + { + var item = _libraryManager.GetStudio(name); + + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // Don't clutter the log + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error refreshing {StudioName}", name); + } + + numComplete++; + double percent = numComplete; + percent /= count; + percent *= 100; + + progress.Report(percent); + } + + var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = [BaseItemKind.Studio], + IsDeadStudio = true, + IsLocked = false + }); + + foreach (var item in deadEntities) + { + _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name); + + _libraryManager.DeleteItem( + item, + new DeleteOptions + { + DeleteFileLocation = false + }, + false); + } + + progress.Report(100); + } +} diff --git a/Emby.Server.Implementations/Localization/Core/ab.json b/Emby.Server.Implementations/Localization/Core/ab.json new file mode 100644 index 00000000..bc6062f4 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ab.json @@ -0,0 +1,3 @@ +{ + "Albums": "аальбомқәа" +} diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json new file mode 100644 index 00000000..1dce5892 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/af.json @@ -0,0 +1,139 @@ +{ + "Artists": "Kunstenare", + "Channels": "Kanale", + "Folders": "Lêergidse", + "Favorites": "Gunstelinge", + "HeaderFavoriteShows": "Gunsteling Vertonings", + "ValueSpecialEpisodeName": "Spesiale - {0}", + "HeaderAlbumArtists": "Album kunstenaars", + "Books": "Boeke", + "HeaderNextUp": "Volgende", + "Movies": "Flieks", + "Shows": "Televisie Reekse", + "HeaderContinueWatching": "Hou aan kyk", + "HeaderFavoriteEpisodes": "Gunsteling Episodes", + "Photos": "Foto's", + "Playlists": "Snitlyste", + "HeaderFavoriteArtists": "Gunsteling Kunstenaars", + "HeaderFavoriteAlbums": "Gunsteling Albums", + "Sync": "Sinkroniseer", + "HeaderFavoriteSongs": "Gunsteling Liedjies", + "Songs": "Liedjies", + "DeviceOnlineWithName": "{0} is aanlyn", + "DeviceOfflineWithName": "{0} is ontkoppel", + "Collections": "Versamelings", + "Inherit": "Ontvang", + "HeaderLiveTV": "Lewendige TV", + "Application": "Program", + "AppDeviceValues": "App: {0}, Toestel: {1}", + "VersionNumber": "Weergawe {0}", + "ValueHasBeenAddedToLibrary": "{0} is by jou media biblioteek bygevoeg", + "UserStoppedPlayingItemWithValues": "{0} het klaar {1} op {2} gespeel", + "UserStartedPlayingItemWithValues": "{0} is besig om {1} op {2} te speel", + "UserPolicyUpdatedWithName": "Gebruiker beleid is verander vir {0}", + "UserPasswordChangedWithName": "Gebruiker {0} se wagwoord is verander", + "UserOnlineFromDevice": "{0} is aanlyn van {1}", + "UserOfflineFromDevice": "{0} is ontkoppel van {1}", + "UserLockedOutWithName": "Gebruiker {0} is uitgesluit", + "UserDownloadingItemWithValues": "{0} is besig om {1} af te laai", + "UserDeletedWithName": "Gebruiker {0} is verwyder", + "UserCreatedWithName": "Gebruiker {0} is geskep", + "User": "Gebruiker", + "TvShows": "TV Programme", + "System": "Stelsel", + "SubtitleDownloadFailureFromForItem": "Ondertitels het misluk om af te laai van {0} vir {1}", + "StartupEmbyServerIsLoading": "Jellyfin Bediener is besig om te laai. Probeer weer in 'n kort tyd.", + "ServerNameNeedsToBeRestarted": "{0} moet herbegin word", + "ScheduledTaskStartedWithName": "{0} het begin", + "ScheduledTaskFailedWithName": "{0} het misluk", + "ProviderValue": "Voorsiener: {0}", + "PluginUpdatedWithName": "{0} was opgedateer", + "PluginUninstalledWithName": "{0} was verwyder", + "PluginInstalledWithName": "{0} is geïnstalleer", + "Plugin": "Inprop module", + "NotificationOptionVideoPlaybackStopped": "Video terugspeel het gestop", + "NotificationOptionVideoPlayback": "Video terugspeel het begin", + "NotificationOptionUserLockedOut": "Gebruiker uitgeslyt", + "NotificationOptionTaskFailed": "Geskeduleerde taak het misluk", + "NotificationOptionServerRestartRequired": "Bediener herbegin nodig", + "NotificationOptionPluginUpdateInstalled": "Nuwe inprop module geïnstalleer", + "NotificationOptionPluginUninstalled": "Inprop module verwyder", + "NotificationOptionPluginInstalled": "Inprop module geïnstalleer", + "NotificationOptionPluginError": "Inprop module het misluk", + "NotificationOptionNewLibraryContent": "Nuwe inhoud bygevoeg", + "NotificationOptionInstallationFailed": "Installasie mislukking", + "NotificationOptionCameraImageUploaded": "Kamera foto is opgelaai", + "NotificationOptionAudioPlaybackStopped": "Oudio terugspeel het gestop", + "NotificationOptionAudioPlayback": "Oudio terugspeel het begin", + "NotificationOptionApplicationUpdateInstalled": "Nuwe program weergawe geïnstalleer", + "NotificationOptionApplicationUpdateAvailable": "Nuwe program weergawe beskikbaar", + "NewVersionIsAvailable": "'n Nuwe Jellyfin Bedienaar weergawe kan afgelaai word.", + "NameSeasonUnknown": "Seisoen Onbekend", + "NameSeasonNumber": "Seisoen {0}", + "NameInstallFailed": "{0} installering het misluk", + "MusicVideos": "Musiek Videos", + "Music": "Musiek", + "MixedContent": "Gemengde inhoud", + "MessageServerConfigurationUpdated": "Bediener konfigurasie is opgedateer", + "MessageNamedServerConfigurationUpdatedWithValue": "Bediener konfigurasie seksie {0} is opgedateer", + "MessageApplicationUpdatedTo": "Jellyfin Bediener is opgedateer na {0}", + "MessageApplicationUpdated": "Jellyfin Bediener is opgedateer", + "Latest": "Nuutste", + "LabelRunningTimeValue": "Werktyd: {0}", + "LabelIpAddressValue": "IP adres: {0}", + "ItemRemovedWithName": "{0} is uit versameling verwyder", + "ItemAddedWithName": "{0} is by die versameling gevoeg", + "HomeVideos": "Tuis Videos", + "HeaderRecordingGroups": "Groep Opnames", + "Genres": "Genres", + "FailedLoginAttemptWithUserName": "Mislukte aanmeldpoging van {0}", + "ChapterNameValue": "Hoofstuk {0}", + "CameraImageUploadedFrom": "'n Nuwe kamera foto is opgelaai vanaf {0}", + "AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer", + "Albums": "Albums", + "TasksChannelsCategory": "Internet kanale", + "TasksApplicationCategory": "aansoek", + "TasksLibraryCategory": "biblioteek", + "TasksMaintenanceCategory": "onderhoud", + "TaskCleanCacheDescription": "Vee kasregister lêers uit wat nie meer deur die stelsel benodig word nie.", + "TaskCleanCache": "Reinig Kasgeheue Lêergids", + "TaskDownloadMissingSubtitlesDescription": "Soek aanlyn vir vermiste onderskrifte gebasseer op metadata verstellings.", + "TaskDownloadMissingSubtitles": "Laai vermiste onderskrifte af", + "TaskRefreshChannelsDescription": "Vervris internet kanaal inligting.", + "TaskRefreshChannels": "Vervris Kanale", + "TaskCleanTranscodeDescription": "Vee transkodering lêers uit wat ouer is as een dag.", + "TaskCleanTranscode": "Reinig Transkoderings Leêrbinder", + "TaskUpdatePluginsDescription": "Laai opgedateerde inprop-sagteware af en installeer inprop-sagteware wat verstel is om outomaties op te dateer.", + "TaskUpdatePlugins": "Dateer Inprop-Sagteware Op", + "TaskRefreshPeopleDescription": "Vervris metadata oor akteurs en regisseurs in u media versameling.", + "TaskRefreshPeople": "Vervris Mense", + "TaskCleanLogsDescription": "Vee loglêers wat ouer as {0} dae is uit.", + "TaskCleanLogs": "Reinig Loglêer Lêervouer", + "TaskRefreshLibraryDescription": "Skandeer u media versameling vir nuwe lêers en verfris metadata.", + "TaskRefreshLibrary": "Skandeer Media Versameling", + "TaskRefreshChapterImagesDescription": "Maak kleinkiekeis (fotos) vir films wat hoofstukke het.", + "TaskRefreshChapterImages": "Verkry Hoofstuk Beelde", + "Undefined": "Ongedefineerd", + "Forced": "Geforseerd", + "Default": "Standaard", + "TaskCleanActivityLogDescription": "Verwyder aktiwiteitsaantekeninge ouer as die opgestelde ouderdom.", + "TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon", + "TaskOptimizeDatabaseDescription": "Komprimeer databasis en verkort vrye ruimte. As hierdie taak uitgevoer word nadat die media versameling geskandeer is of ander veranderings aangebring is wat databasisaanpassings impliseer, kan dit die prestasie verbeter.", + "TaskOptimizeDatabase": "Optimaliseer databasis", + "TaskKeyframeExtractorDescription": "Haal keyframes vanuit video lêers om meer presiese HLS afspeellyste te maak. Dit kan lank duur.", + "TaskKeyframeExtractor": "Keyframe Ekstraktor", + "External": "Ekstern", + "HearingImpaired": "gehoorgestremd", + "TaskRefreshTrickplayImages": "Genereer Fopspeel Beelde", + "TaskRefreshTrickplayImagesDescription": "Skep fopspeel voorskou vir videos in aangeskakelde media versameling.", + "TaskAudioNormalizationDescription": "Skandeer lêers vir oudio-normaliseringsdata.", + "TaskAudioNormalization": "Odio Normalisering", + "TaskCleanCollectionsAndPlaylists": "Maak versamelings en snitlyste skoon", + "TaskCleanCollectionsAndPlaylistsDescription": "Verwyder items uit versamelings en snitlyste wat nie meer bestaan nie.", + "TaskDownloadMissingLyrics": "Laai tekorte lirieke af", + "TaskDownloadMissingLyricsDescription": "Laai lirieke af vir liedjies", + "TaskExtractMediaSegments": "Media Segment Skandeer", + "TaskExtractMediaSegmentsDescription": "Onttrek of verkry mediasegmente van MediaSegment-geaktiveerde inproppe.", + "TaskMoveTrickplayImages": "Migreer Trickplay Beeldligging", + "TaskMoveTrickplayImagesDescription": "Skuif ontstaande trickplay lêers volgens die biblioteekinstellings." +} diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json new file mode 100644 index 00000000..7ce8baef --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -0,0 +1,141 @@ +{ + "Albums": "ألبومات", + "AppDeviceValues": "تطبيق: {0}, جهاز: {1}", + "Application": "تطبيق", + "Artists": "فنانون", + "AuthenticationSucceededWithUserName": "نجحت عملية التوثيق بـ {0}", + "Books": "الكتب", + "CameraImageUploadedFrom": "رُفعت صورة الكاميرا الجديدة من {0}", + "Channels": "القنوات", + "ChapterNameValue": "الفصل {0}", + "Collections": "مجموعات", + "DeviceOfflineWithName": "قُطِع الاتصال ب{0}", + "DeviceOnlineWithName": "{0} متصل", + "FailedLoginAttemptWithUserName": "محاولة تسجيل الدخول فاشلة من {0}", + "Favorites": "المفضلة", + "Folders": "المجلدات", + "Genres": "التصنيفات", + "HeaderAlbumArtists": "فناني الألبوم", + "HeaderContinueWatching": "متابعة المشاهدة", + "HeaderFavoriteAlbums": "الألبومات المفضلة", + "HeaderFavoriteArtists": "الفنانون المفضلون", + "HeaderFavoriteEpisodes": "الحلقات المفضلة", + "HeaderFavoriteShows": "المسلسلات المفضلة", + "HeaderFavoriteSongs": "الأغاني المفضلة", + "HeaderLiveTV": "التلفاز المباشر", + "HeaderNextUp": "التالي", + "HeaderRecordingGroups": "مجموعات التسجيل", + "HomeVideos": "الفيديوهات الشخصية", + "Inherit": "توريث", + "ItemAddedWithName": "أُضيف {0} للمكتبة", + "ItemRemovedWithName": "أُزيل {0} من المكتبة", + "LabelIpAddressValue": "عنوان الآي بي: {0}", + "LabelRunningTimeValue": "مدة التشغيل: {0}", + "Latest": "الأحدث", + "MessageApplicationUpdated": "حُدث خادم Jellyfin", + "MessageApplicationUpdatedTo": "حُدث خادم Jellyfin إلى {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "حُدثت إعدادات الخادم في قسم {0}", + "MessageServerConfigurationUpdated": "حُدثت إعدادات الخادم", + "MixedContent": "محتوى مختلط", + "Movies": "الأفلام", + "Music": "الموسيقى", + "MusicVideos": "الفيديوهات الموسيقية", + "NameInstallFailed": "فشل تثبيت {0}", + "NameSeasonNumber": "الموسم {0}", + "NameSeasonUnknown": "الموسم غير معروف", + "NewVersionIsAvailable": "نسخة جديدة من خادم Jellyfin متوفرة للتحميل.", + "NotificationOptionApplicationUpdateAvailable": "يوجد تحديث للتطبيق", + "NotificationOptionApplicationUpdateInstalled": "نُصب تحديث التطبيق", + "NotificationOptionAudioPlayback": "بدأ تشغيل المقطع الصوتي", + "NotificationOptionAudioPlaybackStopped": "أُوقف تشغيل المقطع الصوتي", + "NotificationOptionCameraImageUploaded": "رُفعت صورة الكاميرا", + "NotificationOptionInstallationFailed": "فشل في التثبيت", + "NotificationOptionNewLibraryContent": "أُضيف محتوى جديدا", + "NotificationOptionPluginError": "فشل في الملحق", + "NotificationOptionPluginInstalled": "ثُبتت الملحق", + "NotificationOptionPluginUninstalled": "تمت إزالة الملحق", + "NotificationOptionPluginUpdateInstalled": "تم تثبيت تحديثات الملحق", + "NotificationOptionServerRestartRequired": "يجب إعادة تشغيل الخادم", + "NotificationOptionTaskFailed": "فشل في المهمة المجدولة", + "NotificationOptionUserLockedOut": "تم إقفال حساب المستخدم", + "NotificationOptionVideoPlayback": "بدأ تشغيل الفيديو", + "NotificationOptionVideoPlaybackStopped": "تم إيقاف تشغيل الفيديو", + "Photos": "الصور", + "Playlists": "قوائم التشغيل", + "Plugin": "الملحق", + "PluginInstalledWithName": "تم تثبيت {0}", + "PluginUninstalledWithName": "تمت إزالة {0}", + "PluginUpdatedWithName": "تم تحديث {0}", + "ProviderValue": "المزود: {0}", + "ScheduledTaskFailedWithName": "فشلت العملية {0}", + "ScheduledTaskStartedWithName": "تم بدء العملية {0}", + "ServerNameNeedsToBeRestarted": "يحتاج {0} لإعادة التشغيل", + "Shows": "العروض", + "Songs": "الأغاني", + "StartupEmbyServerIsLoading": "يتم تحميل خادم Jellyfin . الرجاء المحاولة بعد قليل.", + "SubtitleDownloadFailureFromForItem": "فشل تحميل الترجمات من {0} ل {1}", + "Sync": "مزامنة", + "System": "النظام", + "TvShows": "البرامج التلفزيونية", + "User": "المستخدم", + "UserCreatedWithName": "تم إنشاء المستخدم {0}", + "UserDeletedWithName": "تم حذف المستخدم {0}", + "UserDownloadingItemWithValues": "يقوم {0} بتنزيل {1}", + "UserLockedOutWithName": "تم منع المستخدم {0} من الدخول", + "UserOfflineFromDevice": "تم قطع اتصال {0} من {1}", + "UserOnlineFromDevice": "{0} متصل عبر {1}", + "UserPasswordChangedWithName": "تم تغيير كلمة السر للمستخدم {0}", + "UserPolicyUpdatedWithName": "تم تحديث سياسة المستخدم {0}", + "UserStartedPlayingItemWithValues": "قام {0} ببدء تشغيل {1} على {2}", + "UserStoppedPlayingItemWithValues": "قام {0} بإيقاف تشغيل {1} على {2}", + "ValueHasBeenAddedToLibrary": "تمت اضافت {0} إلى مكتبة الوسائط", + "ValueSpecialEpisodeName": "حلقة خاصه - {0}", + "VersionNumber": "الإصدار {0}", + "TaskCleanCacheDescription": "يحذف الملفات المؤقتة التي لم يعد النظام بحاجة إليها.", + "TaskCleanCache": "حذف الملفات المؤقتة", + "TasksChannelsCategory": "قنوات الإنترنت", + "TasksLibraryCategory": "مكتبة", + "TasksMaintenanceCategory": "صيانة", + "TaskRefreshLibraryDescription": "يفحص مكتبة الوسائط الخاصة بك باحثا عن ملفات جديدة، ومن ثم يُحدث البيانات الوصفية.", + "TaskRefreshLibrary": "افحص مكتبة الوسائط", + "TaskRefreshChapterImagesDescription": "يُنشئ صور مصغرة لمقاطع الفيديو التي تحتوي على فصول.", + "TaskRefreshChapterImages": "استخراج صور الفصل", + "TasksApplicationCategory": "تطبيق", + "TaskDownloadMissingSubtitlesDescription": "يبحث في الإنترنت على الترجمات الناقصة استنادا على البيانات الوصفية.", + "TaskDownloadMissingSubtitles": "تحميل الترجمات الناقصة", + "TaskRefreshChannelsDescription": "يحدث معلومات قنوات الإنترنت.", + "TaskRefreshChannels": "إعادة تحديث القنوات", + "TaskCleanTranscodeDescription": "يحذف ملفات الترميز الأقدم من يوم واحد.", + "TaskCleanTranscode": "حذف ما بمجلد الترميز", + "TaskUpdatePluginsDescription": "تحميل وتثبيت الإضافات التي تم تفعيل التحديث التلقائي لها.", + "TaskUpdatePlugins": "تحديث الإضافات", + "TaskRefreshPeopleDescription": "يقوم بتحديث البيانات الوصفية للممثلين والمخرجين في مكتبة الوسائط الخاصة بك.", + "TaskRefreshPeople": "إعادة تحميل الأشخاص", + "TaskCleanLogsDescription": "يحذف السجلات الأقدم من {0} يوم.", + "TaskCleanLogs": "حذف مسار السجل", + "TaskCleanActivityLogDescription": "يحذف سجل الأنشطة الأقدم من الوقت الذي تم تحديده.", + "TaskCleanActivityLog": "حذف سجل الأنشطة", + "Default": "افتراضي", + "Undefined": "غير معرف", + "Forced": "ملحقة", + "TaskOptimizeDatabaseDescription": "يضغط قاعدة البيانات ويقتطع المساحة الحرة. تشغيل هذه المهمة بعد فحص المكتبة أو إجراء تغييرات أخرى تتضمن تعديلات في قاعدة البيانات قد تؤدي إلى تحسين الأداء.", + "TaskOptimizeDatabase": "تحسين قاعدة البيانات", + "TaskKeyframeExtractorDescription": "يستخرج الإطارات الرئيسية من ملفات الفيديو لكي ينشئ قوائم تشغيل بث HTTP المباشر. قد تستمر هذه العملية لوقت طويل.", + "TaskKeyframeExtractor": "مستخرج الإطار الرئيسي", + "External": "خارجي", + "HearingImpaired": "ضعاف السمع", + "TaskRefreshTrickplayImages": "توليد صور المعاينة السريعة", + "TaskRefreshTrickplayImagesDescription": "يُولّد معاينات تنقل سريع لمقاطع الفيديو ضمن المكتبات المفعّلة.", + "TaskCleanCollectionsAndPlaylists": "حذف المجموعات وقوائم التشغيل", + "TaskCleanCollectionsAndPlaylistsDescription": "حذف عناصر من المجموعات وقوائم التشغيل التي لم تعد موجودة.", + "TaskAudioNormalization": "تسوية الصوت", + "TaskAudioNormalizationDescription": "مسح الملفات لتطبيع بيانات الصوت.", + "TaskDownloadMissingLyrics": "تنزيل عبارات القصيدة", + "TaskDownloadMissingLyricsDescription": "كلمات", + "TaskExtractMediaSegments": "فحص مقاطع الوسائط", + "TaskExtractMediaSegmentsDescription": "يستخرج مقاطع وسائط من إضافات MediaSegment المُفعّلة.", + "TaskMoveTrickplayImages": "تغيير مكان صور المعاينة السريعة", + "TaskMoveTrickplayImagesDescription": "تُنقل ملفات التشغيل السريع الحالية بناءً على إعدادات المكتبة.", + "CleanupUserDataTask": "مهمة تنظيف بيانات المستخدم", + "CleanupUserDataTaskDescription": "مسح جميع بيانات المستخدم (حالة المشاهدة، والحالة المفضلة وما إلى ذلك) من الوسائط التي لم تعد موجودة لمدة 90 يومًا على الأقل." +} diff --git a/Emby.Server.Implementations/Localization/Core/as.json b/Emby.Server.Implementations/Localization/Core/as.json new file mode 100644 index 00000000..7c7dd26e --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/as.json @@ -0,0 +1,43 @@ +{ + "Albums": "এলবাম", + "Application": "আবেদন", + "AppDeviceValues": "এপ্‌: {0}, ডিভাইচ: {1}", + "Artists": "শিল্পী", + "Channels": "চেনেলস", + "Default": "ডিফল্ট", + "AuthenticationSucceededWithUserName": "{0} সফলভাবে প্রমাণিত", + "Books": "পুস্তক", + "Movies": "চলচ্চিত্ৰ", + "CameraImageUploadedFrom": "একটি নতুন ক্যামেরা চিত্র আপলোড করা হয়েছে {0}", + "Collections": "সংগ্রহ", + "HeaderFavoriteShows": "প্রিয় শোসমূহ", + "Latest": "শেহতীয়া", + "MessageApplicationUpdated": "জেলিফিন চাইভাৰ আপডেট কৰা হৈছে", + "MixedContent": "মিশ্ৰিত সমগ্ৰতা", + "NewVersionIsAvailable": "ডাউনলোড কৰিবলৈ জেলিফিন চাইভাৰৰ এটা নতুন সংস্কৰণ উপলব্ধ আছে.", + "NotificationOptionCameraImageUploaded": "কেমেৰাৰ চিত্ৰ আপল'ড কৰা হ'ল", + "External": "বাহ্যিক", + "Favorites": "পছন্দসই", + "Folders": "ফোল্ডাৰ", + "Forced": "বলপূর্বক", + "Genres": "শ্রেণী", + "HeaderAlbumArtists": "অ্যালবাম শিল্পী", + "HeaderContinueWatching": "দেখা চালিয়ে যান", + "FailedLoginAttemptWithUserName": "লগইন ব্যর্থ চেষ্টা কৰা হৈছে থেকে {0}", + "HeaderFavoriteAlbums": "প্রিয় অ্যালবামসমূহ", + "HeaderFavoriteArtists": "প্রিয় শিল্পীসমূহ", + "HeaderFavoriteEpisodes": "প্রিয় পর্বসমূহ", + "HeaderFavoriteSongs": "প্ৰিয় গীত", + "HeaderLiveTV": "প্ৰতিবেদন টিভি", + "HeaderNextUp": "পৰৱৰ্তী অংশ", + "HeaderRecordingGroups": "অলংকৰণ গোষ্ঠীসমূহ", + "HearingImpaired": "শ্ৰবণ অক্ষম", + "HomeVideos": "ঘৰৰ ভিডিঅ'সমূহ", + "Inherit": "উত্তপ্ত কৰা", + "MessageServerConfigurationUpdated": "চাইভাৰ কনফিগাৰেশ্যন আপডেট কৰা হৈছে", + "NotificationOptionApplicationUpdateAvailable": "অ্যাপ্লিকেশ্যন আপডেট উপলব্ধ", + "NotificationOptionApplicationUpdateInstalled": "অ্যাপ্লিকেশ্যন আপডেট ইনষ্টল কৰা হ'ল", + "NotificationOptionAudioPlayback": "অডিঅ' প্লেবেক আৰম্ভ হ'ল", + "NotificationOptionAudioPlaybackStopped": "অডিঅ' প্লেবেক আঁতৰ হ'ল", + "NotificationOptionInstallationFailed": "ইনষ্টলেশ্যন ব্যৰ্থতা" +} diff --git a/Emby.Server.Implementations/Localization/Core/be.json b/Emby.Server.Implementations/Localization/Core/be.json new file mode 100644 index 00000000..cb11cc08 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/be.json @@ -0,0 +1,141 @@ +{ + "Sync": "Сінхранізаваць", + "Playlists": "Плэй-лісты", + "Latest": "Апошняе", + "LabelIpAddressValue": "IP-адрас: {0}", + "ItemAddedWithName": "{0} дададзены ў бібліятэку", + "MessageApplicationUpdated": "Сервер Jellyfin абноўлены", + "NotificationOptionApplicationUpdateInstalled": "Абнаўленне праграмы ўсталявана", + "PluginInstalledWithName": "{0} быў усталяваны", + "UserCreatedWithName": "Карыстальнік {0} быў створаны", + "Albums": "Альбомы", + "Application": "Праграма", + "AuthenticationSucceededWithUserName": "{0} паспяхова аўтарызаваны", + "Channels": "Каналы", + "ChapterNameValue": "Раздзел {0}", + "Collections": "Калекцыі", + "Default": "Прадвызначана", + "FailedLoginAttemptWithUserName": "Няўдалая спроба ўваходу з {0}", + "Folders": "Папкі", + "Favorites": "Абранае", + "External": "Знешні", + "Genres": "Жанры", + "HeaderContinueWatching": "Працягнуць прагляд", + "HeaderFavoriteAlbums": "Абраныя альбомы", + "HeaderFavoriteEpisodes": "Абраныя серыі", + "HeaderFavoriteShows": "Абраныя шоу", + "HeaderFavoriteSongs": "Абраныя песні", + "HeaderLiveTV": "Прамы эфір", + "HeaderAlbumArtists": "Выканаўцы альбома", + "LabelRunningTimeValue": "Працягласць: {0}", + "HomeVideos": "Хатнія відэа", + "ItemRemovedWithName": "{0} выдалены з бібліятэкі", + "MessageApplicationUpdatedTo": "Сервер Jellyfin абноўлены да версіі {0}", + "Movies": "Фільмы", + "Music": "Музыка", + "MusicVideos": "Музычныя кліпы", + "NameInstallFailed": "Усталяванне {0} не атрымалася", + "NameSeasonNumber": "Сезон {0}", + "NotificationOptionApplicationUpdateAvailable": "Даступна абнаўленне праграмы", + "NotificationOptionPluginInstalled": "Плагін усталяваны", + "NotificationOptionPluginUpdateInstalled": "Абнаўленне плагіна ўсталявана", + "NotificationOptionServerRestartRequired": "Патрабуецца перазапуск сервера", + "Photos": "Фотаздымкі", + "Plugin": "Плагін", + "PluginUninstalledWithName": "{0} быў выдалены", + "PluginUpdatedWithName": "{0} быў абноўлены", + "ProviderValue": "Пастаўшчык: {0}", + "Songs": "Песні", + "System": "Сістэма", + "User": "Карыстальнік", + "UserDeletedWithName": "Карыстальнік {0} быў выдалены", + "UserDownloadingItemWithValues": "{0} спампоўваецца {1}", + "TaskOptimizeDatabase": "Аптымізацыя базы даных", + "Artists": "Выканаўцы", + "UserOfflineFromDevice": "{0} адлучыўся ад {1}", + "UserPolicyUpdatedWithName": "Палітыка карыстальніка абноўлена для {0}", + "TaskCleanActivityLogDescription": "Выдаляе запісы старэйшыя за зададзены ўзрост ў журнале актыўнасці.", + "TaskRefreshChapterImagesDescription": "Стварае мініяцюры для відэа, якія маюць раздзелы.", + "TaskCleanLogsDescription": "Выдаляе файлы журналу, якім больш за {0} дзён.", + "TaskUpdatePluginsDescription": "Спампоўвае і ўсталёўвае абнаўленні для плагінаў, якія сканфігураваныя на аўтаматычнае абнаўленне.", + "TaskRefreshChannelsDescription": "Абнаўляе інфармацыю аб інтэрнэт-канале.", + "TaskDownloadMissingSubtitlesDescription": "Шукае ў інтэрнэце адсутныя субцітры на аснове канфігурацыі метаданых.", + "TaskOptimizeDatabaseDescription": "Сціскае базу даных і вызваляе вольную прастору. Выкананне гэтай задачы пасля сканіравання бібліятэкі або іншых змяненняў, якія мадыфікуюць базу даных, можа палепшыць прадукцыйнасць.", + "TaskKeyframeExtractor": "Экстрактар ключавых кадраў", + "TasksApplicationCategory": "Праграма", + "AppDeviceValues": "Праграма: {0}, Прылада: {1}", + "Books": "Кнігі", + "CameraImageUploadedFrom": "Новая выява камеры была загружана з {0}", + "DeviceOfflineWithName": "{0} адлучыўся", + "DeviceOnlineWithName": "{0} падлучаны", + "Forced": "Прымусова", + "HeaderRecordingGroups": "Групы запісаў", + "HeaderNextUp": "Наступнае", + "HeaderFavoriteArtists": "Абраныя выканаўцы", + "HearingImpaired": "Са слабым слыхам", + "Inherit": "Атрымаць у спадчыну", + "MessageNamedServerConfigurationUpdatedWithValue": "Канфігурацыя сервера (секцыя {0}) абноўлена", + "MessageServerConfigurationUpdated": "Канфігурацыя сервера абноўлена", + "MixedContent": "Змешаны змест", + "NameSeasonUnknown": "Невядомы сезон", + "NotificationOptionInstallationFailed": "Збой усталёўкі", + "NewVersionIsAvailable": "Новая версія сервера Jellyfin даступная для cпампоўкі.", + "NotificationOptionCameraImageUploaded": "Выява камеры запампавана", + "NotificationOptionAudioPlaybackStopped": "Прайграванне аўдыя спынена", + "NotificationOptionAudioPlayback": "Прайграванне аўдыя пачалося", + "NotificationOptionNewLibraryContent": "Дададзены новы кантэнт", + "NotificationOptionPluginError": "Збой плагіна", + "NotificationOptionPluginUninstalled": "Плагін выдалены", + "NotificationOptionTaskFailed": "Збой запланаванага задання", + "NotificationOptionUserLockedOut": "Карыстальнік заблакіраваны", + "NotificationOptionVideoPlayback": "Пачалося прайграванне відэа", + "NotificationOptionVideoPlaybackStopped": "Прайграванне відэа спынена", + "ScheduledTaskFailedWithName": "{0} не атрымалася", + "ScheduledTaskStartedWithName": "{0} пачалося", + "ServerNameNeedsToBeRestarted": "{0} патрабуе перазапуску", + "Shows": "Шоу", + "StartupEmbyServerIsLoading": "Jellyfin Server загружаецца. Калі ласка, паўтарыце спробу крыху пазней.", + "SubtitleDownloadFailureFromForItem": "Субцітры для {1} не ўдалося спампаваць з {0}", + "TvShows": "Тэлепраграма", + "Undefined": "Нявызначана", + "UserLockedOutWithName": "Карыстальнік {0} быў заблакіраваны", + "UserOnlineFromDevice": "{0} падключаны з {1}", + "UserPasswordChangedWithName": "Пароль быў зменены для карыстальніка {0}", + "UserStartedPlayingItemWithValues": "{0} прайграваецца {1} на {2}", + "UserStoppedPlayingItemWithValues": "{0} скончыў прайграванне {1} на {2}", + "ValueHasBeenAddedToLibrary": "{0} быў дададзены ў вашу медыятэку", + "ValueSpecialEpisodeName": "Спецвыпуск - {0}", + "VersionNumber": "Версія {0}", + "TasksMaintenanceCategory": "Абслугоўванне", + "TasksLibraryCategory": "Бібліятэка", + "TasksChannelsCategory": "Інтэрнэт-каналы", + "TaskCleanActivityLog": "Ачысціць журнал актыўнасці", + "TaskCleanCache": "Ачысціць кэш", + "TaskCleanCacheDescription": "Выдаляе файлы кэша, якія больш не патрэбныя сістэме.", + "TaskRefreshChapterImages": "Вынуць выявы раздзелаў", + "TaskRefreshLibrary": "Сканаваць бібліятэку", + "TaskRefreshLibraryDescription": "Сканіруе вашу медыятэку на наяўнасць новых файлаў і абнаўляе метаданыя.", + "TaskCleanLogs": "Ачысціць журнал", + "TaskRefreshPeople": "Абнавіць выканаўцаў", + "TaskRefreshPeopleDescription": "Абнаўленне метаданых для акцёраў і рэжысёраў у вашай медыятэцы.", + "TaskUpdatePlugins": "Абнавіць плагіны", + "TaskCleanTranscode": "Ачысціць каталог перакадзіравання", + "TaskCleanTranscodeDescription": "Выдаляе перакадзіраваныя файлы, старэйшыя за адзін дзень.", + "TaskRefreshChannels": "Абнавіць каналы", + "TaskDownloadMissingSubtitles": "Спампаваць адсутныя субцітры", + "TaskKeyframeExtractorDescription": "Выдае ключавыя кадры з відэафайлаў для стварэння больш дакладных плэй-лістоў HLS. Гэта задача можа працягнуцца шмат часу.", + "TaskRefreshTrickplayImages": "Стварыць выявы Trickplay", + "TaskRefreshTrickplayImagesDescription": "Стварае перадпрагляды відэаролікаў для Trickplay у падключаных бібліятэках.", + "TaskCleanCollectionsAndPlaylists": "Ачысціце калекцыі і плэй-лісты", + "TaskCleanCollectionsAndPlaylistsDescription": "Выдаляе элементы з калекцый і плэй-лістоў, якія больш не існуюць.", + "TaskAudioNormalizationDescription": "Скануе файлы на прадмет нармалізацыі гуку.", + "TaskAudioNormalization": "Нармалізацыя гуку", + "TaskExtractMediaSegmentsDescription": "Выдае або атрымлівае медыясегменты з убудоў з падтрымкай MediaSegment.", + "TaskMoveTrickplayImagesDescription": "Перамяшчае існуючыя файлы trickplay у адпаведнасці з наладамі бібліятэкі.", + "TaskDownloadMissingLyrics": "Спампаваць адсутныя тэксты песняў", + "TaskDownloadMissingLyricsDescription": "Спампоўвае тэксты для песняў", + "TaskExtractMediaSegments": "Сканіраванне медыя-сегмента", + "TaskMoveTrickplayImages": "Перанесці месцазнаходжанне выявы Trickplay", + "CleanupUserDataTask": "Задача па ачыстцы даных карыстальніка", + "CleanupUserDataTaskDescription": "Ачышчае ўсе даныя карыстальніка (стан прагляду, абранае і г.д.) для медыяфайлаў, што адсутнічаюць больш за 90 дзён." +} diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json new file mode 100644 index 00000000..92b8e5d5 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json @@ -0,0 +1,141 @@ +{ + "Albums": "Албуми", + "AppDeviceValues": "Програма: {0}, Устройство: {1}", + "Application": "Програма", + "Artists": "Артисти", + "AuthenticationSucceededWithUserName": "{0} се удостовери успешно", + "Books": "Книги", + "CameraImageUploadedFrom": "Нова снимка от камера беше качена от {0}", + "Channels": "Канали", + "ChapterNameValue": "Глава {0}", + "Collections": "Колекции", + "DeviceOfflineWithName": "{0} се разкачи", + "DeviceOnlineWithName": "{0} е свързан", + "FailedLoginAttemptWithUserName": "Неуспешен опит за влизане от {0}", + "Favorites": "Любими", + "Folders": "Папки", + "Genres": "Жанрове", + "HeaderAlbumArtists": "Изпълнители на албуми", + "HeaderContinueWatching": "Продължаване на гледането", + "HeaderFavoriteAlbums": "Любими албуми", + "HeaderFavoriteArtists": "Любими изпълнители", + "HeaderFavoriteEpisodes": "Любими епизоди", + "HeaderFavoriteShows": "Любими сериали", + "HeaderFavoriteSongs": "Любими песни", + "HeaderLiveTV": "Телевизия на живо", + "HeaderNextUp": "Следва", + "HeaderRecordingGroups": "Запис групи", + "HomeVideos": "Домашни Клипове", + "Inherit": "Наследяване", + "ItemAddedWithName": "{0} е добавено към библиотеката", + "ItemRemovedWithName": "{0} е премахнато от библиотеката", + "LabelIpAddressValue": "IP адрес: {0}", + "LabelRunningTimeValue": "Продължителност: {0}", + "Latest": "Последни", + "MessageApplicationUpdated": "Сървърът беше обновен", + "MessageApplicationUpdatedTo": "Сървърът беше обновен до {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Секцията {0} от сървърната конфигурация беше актуализирана", + "MessageServerConfigurationUpdated": "Конфигурацията на сървъра беше актуализирана", + "MixedContent": "Смесено съдържание", + "Movies": "Филми", + "Music": "Музика", + "MusicVideos": "Музикални Видеа", + "NameInstallFailed": "{0} не можа да се инсталира", + "NameSeasonNumber": "Сезон {0}", + "NameSeasonUnknown": "Неразпознат сезон", + "NewVersionIsAvailable": "Нова версия на Джелифин сървъра е достъпна за сваляне.", + "NotificationOptionApplicationUpdateAvailable": "Налично е обновление на програмата", + "NotificationOptionApplicationUpdateInstalled": "Обновлението на програмата е инсталирано", + "NotificationOptionAudioPlayback": "Възпроизвеждането на звук започна", + "NotificationOptionAudioPlaybackStopped": "Възпроизвеждането на звук е спряно", + "NotificationOptionCameraImageUploaded": "Изображението от фотоапарата е качено", + "NotificationOptionInstallationFailed": "Неуспешно инсталиране", + "NotificationOptionNewLibraryContent": "Добавено е ново съдържание", + "NotificationOptionPluginError": "Грешка в приставка", + "NotificationOptionPluginInstalled": "Приставката е инсталирана", + "NotificationOptionPluginUninstalled": "Приставката е деинсталирана", + "NotificationOptionPluginUpdateInstalled": "Обновлението на приставката е инсталирано", + "NotificationOptionServerRestartRequired": "Сървърът трябва да се рестартира", + "NotificationOptionTaskFailed": "Грешка в планирана задача", + "NotificationOptionUserLockedOut": "Потребителят е заключен", + "NotificationOptionVideoPlayback": "Възпроизвеждането на видео започна", + "NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно", + "Photos": "Снимки", + "Playlists": "Списъци", + "Plugin": "Добавка", + "PluginInstalledWithName": "{0} е инсталиранa", + "PluginUninstalledWithName": "{0} е деинсталиранa", + "PluginUpdatedWithName": "{0} е обновенa", + "ProviderValue": "Доставчик: {0}", + "ScheduledTaskFailedWithName": "{0} се провали", + "ScheduledTaskStartedWithName": "{0} започна", + "ServerNameNeedsToBeRestarted": "{0} трябва да се рестартира", + "Shows": "Сериали", + "Songs": "Песни", + "StartupEmbyServerIsLoading": "Сървърът зарежда. Моля, опитайте отново след малко.", + "SubtitleDownloadFailureFromForItem": "Субтитрите за {1} от {0} не можаха да бъдат изтеглени", + "Sync": "Синхронизиране", + "System": "Система", + "TvShows": "Телевизионни сериали", + "User": "Потребител", + "UserCreatedWithName": "Потребителят {0} е създаден", + "UserDeletedWithName": "Потребителят {0} е изтрит", + "UserDownloadingItemWithValues": "{0} изтегля {1}", + "UserLockedOutWithName": "Потребител {0} се заключи", + "UserOfflineFromDevice": "{0} се разкачи от {1}", + "UserOnlineFromDevice": "{0} е на линия от {1}", + "UserPasswordChangedWithName": "Паролата на потребителя {0} е променена", + "UserPolicyUpdatedWithName": "Потребителската политика за {0} се актуализира", + "UserStartedPlayingItemWithValues": "{0} пусна {1}", + "UserStoppedPlayingItemWithValues": "{0} спря {1}", + "ValueHasBeenAddedToLibrary": "{0} беше добавен във Вашата библиотека", + "ValueSpecialEpisodeName": "Специални - {0}", + "VersionNumber": "Версия {0}", + "TaskDownloadMissingSubtitlesDescription": "Търси Интернет за липсващи субтитри, на база конфигурацията за мета-данни.", + "TaskDownloadMissingSubtitles": "Изтегляне на липсващи субтитри", + "TaskRefreshChannelsDescription": "Обновява информацията за интернет канала.", + "TaskRefreshChannels": "Обновяване на Канали", + "TaskCleanTranscodeDescription": "Изтрива транскодирани файлове по-стари от един ден.", + "TaskCleanTranscode": "Изчиства директорията за транскодиране", + "TaskUpdatePluginsDescription": "Изтегля и инсталира актуализации за добавките, които са настроени за автоматична актуализация.", + "TaskUpdatePlugins": "Актуализира добавките", + "TaskRefreshPeopleDescription": "Актуализира мета-данните за артистите и режисьорите за Вашата медийна библиотека.", + "TaskRefreshPeople": "Обновяване на участниците", + "TaskCleanLogsDescription": "Изтрива лог файлове по-стари от {0} дни.", + "TaskCleanLogs": "Изчисти директорията с логове", + "TaskRefreshLibraryDescription": "Сканира Вашата библиотека с медия за нови файлове и обновява мета-данните.", + "TaskRefreshLibrary": "Сканиране на библиотеката с медия", + "TaskRefreshChapterImagesDescription": "Създава иконки за видеа, които имат епизоди.", + "TaskRefreshChapterImages": "Извличане на изображения за епизода", + "TaskCleanCacheDescription": "Изтриване на ненужните от системата файлове.", + "TaskCleanCache": "Изчистване на Кеш-директорията", + "TasksChannelsCategory": "Интернет Канали", + "TasksApplicationCategory": "Приложение", + "TasksLibraryCategory": "Библиотека", + "TasksMaintenanceCategory": "Поддръжка", + "Undefined": "Неопределено", + "Forced": "Принудително", + "Default": "По подразбиране", + "TaskCleanActivityLogDescription": "Изтрива записите в дневника с активност по стари от конфигурираната възраст.", + "TaskCleanActivityLog": "Изчисти дневника с активност", + "TaskOptimizeDatabaseDescription": "Прави базата данни по-компактна и освобождава място. Пускането на тази задача след сканиране на библиотеката или правене на други промени, свързани с модификации на базата данни, може да подобри производителността.", + "TaskOptimizeDatabase": "Оптимизирай базата данни", + "TaskKeyframeExtractorDescription": "Извличат се ключови кадри от видеофайловете ,за да се създаде по точен HLS списък . Задачата може да отнеме много време.", + "TaskKeyframeExtractor": "Извличане на ключови кадри", + "External": "Външен", + "HearingImpaired": "Увреден слух", + "TaskRefreshTrickplayImages": "Генерирай изображение", + "TaskRefreshTrickplayImagesDescription": "Създава прегледи на Trickplay за видеа в активирани библиотеки.", + "TaskDownloadMissingLyrics": "Свали липсващи текстове", + "TaskDownloadMissingLyricsDescription": "Свали текстове за песни", + "TaskCleanCollectionsAndPlaylists": "Изчисти колекциите и плейлистите", + "TaskCleanCollectionsAndPlaylistsDescription": "Премахни несъществуващи файлове в колекциите и плейлистите.", + "TaskAudioNormalization": "Нормализиране на звука", + "TaskAudioNormalizationDescription": "Сканирай файловете за нормализация на звука.", + "TaskExtractMediaSegmentsDescription": "Изважда медиини сегменти от MediaSegment плъгини.", + "TaskMoveTrickplayImages": "Мигриране на Локацията за Trickplay изображения", + "TaskMoveTrickplayImagesDescription": "Премества съществуващите trickplay изображения спрямо настройките на библиотеката.", + "TaskExtractMediaSegments": "Сканиране за сегменти", + "CleanupUserDataTask": "Задача за почистване на потребителски данни", + "CleanupUserDataTaskDescription": "Почиства всички потребителски данни (статус на гледане, любими и т.н.) от медия, която вече не е налична от поне 90 дни." +} diff --git a/Emby.Server.Implementations/Localization/Core/bn.json b/Emby.Server.Implementations/Localization/Core/bn.json new file mode 100644 index 00000000..87677377 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/bn.json @@ -0,0 +1,141 @@ +{ + "DeviceOnlineWithName": "{0}-এর সাথে সংযুক্ত হয়েছে", + "DeviceOfflineWithName": "{0}-এর সাথে সংযোগ বিচ্ছিন্ন হয়েছে", + "Collections": "সংগ্রহশালা", + "ChapterNameValue": "অধ্যায় {0}", + "Channels": "চ্যানেলসমূহ", + "CameraImageUploadedFrom": "{0} থেকে একটি নতুন ক্যামেরার চিত্র আপলোড করা হয়েছে", + "Books": "পুস্তকসমূহ", + "AuthenticationSucceededWithUserName": "{0} সফলভাবে অথেন্টিকেট করেছেন", + "Artists": "শিল্পীগণ", + "Application": "অ্যাপ্লিকেশন", + "Albums": "অ্যালবামসমূহ", + "HeaderFavoriteEpisodes": "প্রিয় পর্বগুলো", + "HeaderFavoriteArtists": "প্রিয় শিল্পীরা", + "HeaderFavoriteAlbums": "প্রিয় এলবামগুলো", + "HeaderContinueWatching": "দেখতে থাকুন", + "HeaderAlbumArtists": "অ্যালবাম শিল্পীবৃন্দ", + "Genres": "ধরণ", + "Folders": "ফোল্ডারসমূহ", + "Favorites": "পছন্দসমূহ", + "FailedLoginAttemptWithUserName": "{0} লগিন করতে ব্যর্থ হয়েছে", + "AppDeviceValues": "অ্যাপ: {0}, ডিভাইস: {1}", + "VersionNumber": "সংস্করণ {0}", + "ValueSpecialEpisodeName": "বিশেষ পর্ব - {0}", + "ValueHasBeenAddedToLibrary": "আপনার লাইব্রেরিতে {0} যোগ করা হয়েছে", + "UserStoppedPlayingItemWithValues": "{2}তে {1} প্লে শেষ করেছেন {0}", + "UserStartedPlayingItemWithValues": "{2}তে {1} প্লে করেছেন {0}", + "UserPolicyUpdatedWithName": "{0} এর জন্য ব্যবহার নীতি আপডেট করা হয়েছে", + "UserPasswordChangedWithName": "ব্যবহারকারী {0} এর পাসওয়ার্ড পরিবর্তিত হয়েছে", + "UserOnlineFromDevice": "{0}, {1} থেকে অনলাইন আছে", + "UserOfflineFromDevice": "{0} {1} থেকে বিচ্ছিন্ন হয়ে গেছে", + "UserLockedOutWithName": "ব্যবহারকারী {0} ঢুকতে পারছে না", + "UserDownloadingItemWithValues": "{0}, {1} ডাউনলোড করছে", + "UserDeletedWithName": "ব্যবহারকারী {0}কে বাদ দেয়া হয়েছে", + "UserCreatedWithName": "ব্যবহারকারী {0} সৃষ্টি করা হয়েছে", + "User": "ব্যবহারকারী", + "TvShows": "টিভি শোগুলো", + "System": "সিস্টেম", + "Sync": "সমন্বয় করুন", + "SubtitleDownloadFailureFromForItem": "{0} থেকে {1} এর জন্য সাবটাইটেল ডাউনলোড ব্যর্থ হয়েছে", + "StartupEmbyServerIsLoading": "জেলিফিন সার্ভার লোড হচ্ছে। দয়া করে একটু পরে আবার চেষ্টা করুন।", + "Songs": "সঙ্গীত সমূহ", + "Shows": "শো সমূহ", + "ServerNameNeedsToBeRestarted": "{0} রিস্টার্ট করা প্রয়োজন", + "ScheduledTaskStartedWithName": "{0} শুরু হয়েছে", + "ScheduledTaskFailedWithName": "{0} ব্যর্থ", + "ProviderValue": "প্রদানকারী: {0}", + "PluginUpdatedWithName": "{0} আপডেট করা হয়েছে", + "PluginUninstalledWithName": "{0} আনইন্সটল হয়েছে", + "PluginInstalledWithName": "{0} ইন্সটল হয়েছে", + "Plugin": "প্লাগিন", + "Playlists": "প্লে লিস্ট সমূহ", + "Photos": "ছবিসমূহ", + "NotificationOptionVideoPlaybackStopped": "ভিডিও প্লেব্যাক বন্ধ হয়েছে", + "NotificationOptionVideoPlayback": "ভিডিও প্লেব্যাক শুরু হয়েছে", + "NotificationOptionUserLockedOut": "ব্যবহারকারী লক আউট হয়েছে", + "NotificationOptionTaskFailed": "পরিকল্পিত কাজটি ব্যর্থ", + "NotificationOptionServerRestartRequired": "সার্ভার রিস্টার্ট করা লাগবে", + "NotificationOptionPluginUpdateInstalled": "প্লাগিন আপডেট ইন্সটল হয়েছে", + "NotificationOptionPluginUninstalled": "প্লাগিন আনইনষ্টল হয়েছে", + "NotificationOptionPluginInstalled": "প্লাগিন ইন্সটল করা হয়েছে", + "NotificationOptionPluginError": "প্লাগিন ব্যর্থ", + "NotificationOptionNewLibraryContent": "নতুন কন্টেন্ট যোগ করা হয়েছে", + "NotificationOptionInstallationFailed": "ইন্সটল ব্যর্থ হয়েছে", + "NotificationOptionCameraImageUploaded": "ক্যামেরার ছবি আপলোড হয়েছে", + "NotificationOptionAudioPlaybackStopped": "গান বাজা বন্ধ হয়েছে", + "NotificationOptionAudioPlayback": "গান বাজা শুরু হয়েছে", + "NotificationOptionApplicationUpdateInstalled": "এপ্লিকেশনের আপডেট ইনস্টল করা হয়েছে", + "NotificationOptionApplicationUpdateAvailable": "এপ্লিকেশনের আপডেট রয়েছে", + "NewVersionIsAvailable": "জেলিফিন সার্ভারের একটি নতুন ভার্শন ডাউনলোডের জন্য তৈরী।", + "NameSeasonUnknown": "সিজন অজানা", + "NameSeasonNumber": "সিজন {0}", + "NameInstallFailed": "{0} ইন্সটল ব্যর্থ", + "MusicVideos": "সঙ্গীত ভিডিয়ো সমূহ", + "Music": "গান", + "Movies": "চলচ্চিত্রসমূহ", + "MixedContent": "মিশ্র কন্টেন্ট", + "MessageServerConfigurationUpdated": "সার্ভারের কনফিগারেশন আপডেট করা হয়েছে", + "HeaderRecordingGroups": "রেকর্ডিং গ্রুপগুলো", + "MessageNamedServerConfigurationUpdatedWithValue": "সার্ভার কনফিগারেশন সেকশন {0} আপডেট করা হয়েছে", + "MessageApplicationUpdatedTo": "জেলিফিন সার্ভার {0} তে আপডেট করা হয়েছে", + "MessageApplicationUpdated": "জেলিফিন সার্ভার আপডেট করা হয়েছে", + "Latest": "সর্বশেষ", + "LabelRunningTimeValue": "চলার সময়: {0}", + "LabelIpAddressValue": "আইপি এড্রেস: {0}", + "ItemRemovedWithName": "{0} লাইব্রেরি থেকে বাদ দেয়া হয়েছে", + "ItemAddedWithName": "{0} লাইব্রেরিতে যোগ করা হয়েছে", + "Inherit": "উত্তরাধিকারসূত্র থেকে গ্রহণ করুন", + "HomeVideos": "হোম ভিডিও", + "HeaderNextUp": "এরপরে আসছে", + "HeaderLiveTV": "লাইভ টিভি", + "HeaderFavoriteSongs": "প্রিয় গানগুলো", + "HeaderFavoriteShows": "প্রিয় শোগুলো", + "TasksLibraryCategory": "লাইব্রেরি", + "TasksMaintenanceCategory": "রক্ষণাবেক্ষণ", + "TaskRefreshLibrary": "স্ক্যান মিডিয়া লাইব্রেরি", + "TaskRefreshChapterImagesDescription": "যেসব ভিডিওতে চ্যাপ্টার রয়েছে, তাদের জন্য থাম্বনেইল তৈরি করবে।", + "TaskRefreshChapterImages": "চ্যাপ্টার ইমেজ বের করুন", + "TaskCleanCacheDescription": "সিস্টেমের অপ্রয়োজনীয় ক্যাশ ফাইলগুলো মুছে ফেলবে।", + "TaskCleanCache": "ক্লিন ক্যাশ ডিরেক্টরি", + "TasksChannelsCategory": "ইন্টারনেট চ্যানেল", + "TasksApplicationCategory": "অ্যাপ্লিকেশন", + "TaskDownloadMissingSubtitlesDescription": "মেটাডেটা কনফিগারেশনের উপর ভিত্তি করে অনুপস্থিত সাবটাইটেলগুলির জন্য ইন্টারনেট অনুসন্ধান করে।", + "TaskDownloadMissingSubtitles": "অনুপস্থিত সাবটাইটেলগুলি ডাউনলোড করুন", + "TaskRefreshChannelsDescription": "ইন্টারনেট চ্যানেল তথ্য রিফ্রেশ করুন।", + "TaskRefreshChannels": "চ্যানেল রিফ্রেশ করুন", + "TaskCleanTranscodeDescription": "এক দিনেরও বেশি পুরানো ট্রান্সকোড ফাইলগুলি মুছে ফেলবে।", + "TaskCleanTranscode": "ট্রান্সকোড ডিরেক্টরি ক্লিন করুন", + "TaskUpdatePluginsDescription": "স্বয়ংক্রিয়ভাবে আপডেট কনফিগার করা প্লাগইনগুলির জন্য আপডেট ডাউনলোড এবং ইনস্টল করুন।", + "TaskUpdatePlugins": "আপডেট প্লাগইন", + "TaskRefreshPeopleDescription": "আপনার মিডিয়া লাইব্রেরিতে অভিনেতা এবং পরিচালকদের জন্য মেটাডাটা আপডেট করবে।", + "TaskRefreshPeople": "ব্যক্তিদের তথ্য রিফ্রেশ", + "TaskCleanLogsDescription": "{0} দিনের বেশী পুরানো লগ ফাইলগুলি মুছে ফেলবে।", + "TaskCleanLogs": "ক্লিন লগ ডিরেক্টরি", + "TaskRefreshLibraryDescription": "নতুন ফাইলের জন্য মিডিয়া লাইব্রেরি স্ক্যান এবং মেটাডাটা রিফ্রেশ করবে।", + "Undefined": "অসঙ্গায়িত", + "Forced": "জোরকরে", + "TaskCleanActivityLogDescription": "নির্ধারিত সময়ের আগের অ্যাক্টিভিটি লগ মুছে দিবে।", + "TaskCleanActivityLog": "অ্যাক্টিভিটি লগ মুছুন", + "Default": "ডিফল্ট", + "HearingImpaired": "শ্রবণ প্রতিবন্ধী", + "TaskOptimizeDatabaseDescription": "তথ্যভাণ্ডার সুবিন্যস্ত করে ও অব্যবহৃত জায়গা ছেড়ে দেয়। লাইব্রেরী স্ক্যান অথবা যেকোনো তথ্যভাণ্ডার পরিবর্তনের পর এই প্রক্রিয়া চালালে তথ্যভাণ্ডারের তথ্য প্রদান দ্রুততর হতে পারে।", + "External": "বাহ্যিক", + "TaskOptimizeDatabase": "তথ্যভাণ্ডার সুবিন্যাস", + "TaskKeyframeExtractor": "কি-ফ্রেম নিষ্কাশক", + "TaskKeyframeExtractorDescription": "ভিডিয়ো থেকে কি-ফ্রেম নিষ্কাশনের মাধ্যমে অধিকতর সঠিক HLS প্লে লিস্ট তৈরী করে। এই প্রক্রিয়া দীর্ঘ সময় ধরে চলতে পারে।", + "TaskRefreshTrickplayImages": "ট্রিকপ্লে ইমেজ তৈরি", + "TaskRefreshTrickplayImagesDescription": "সক্ষম লাইব্রেরিতে ভিডিওর জন্য ট্রিকপ্লে প্রিভিউ তৈরি করে।", + "TaskDownloadMissingLyricsDescription": "গানের জন্য লিরিকস ডাউনলোড করুন", + "TaskCleanCollectionsAndPlaylists": "কালেকশন এবং প্লেলিস্ট পরিষ্কার করুন", + "TaskCleanCollectionsAndPlaylistsDescription": "কালেকশন এবং প্লেলিস্ট থেকে আইটেমগুলি সরিয়ে দেয় যা আর বিদ্যমান নেই।", + "TaskExtractMediaSegments": "মিডিয়া সেগমেন্ট স্ক্যান", + "TaskExtractMediaSegmentsDescription": "মিডিয়া সেগমেন্ট সক্ষম প্লাগইনগুলি থেকে মিডিয়া সেগমেন্ট বের করে বা অর্জন করে।", + "TaskDownloadMissingLyrics": "অনুপস্থিত গান ডাউনলোড করুন", + "TaskMoveTrickplayImagesDescription": "লাইব্রেরির সেটিং অনুযায়ী বিদ্যমান ট্রিকপ্লে ফাইলগুলো সরিয়ে নেবে।", + "TaskAudioNormalizationDescription": "অডিও নর্মালাইজেশন তথ্যের জন্য ফাইল স্ক্যান করবে।", + "CleanupUserDataTaskDescription": "৯০ দিন বা তার বেশি সময় ধরে অনুপস্থিত মিডিয়া থেকে সকল ব্যবহারকারীর ডেটা (ওয়াচ স্টেট, ফেভারিট স্ট্যাটাস ইত্যাদি) মুছে ফেলবে।", + "TaskMoveTrickplayImages": "ট্রিকপ্লে ইমেজের অবস্থান পরিবর্তন", + "TaskAudioNormalization": "অডিও নর্মলাইজেশন", + "CleanupUserDataTask": "ইউজার ডেটা ক্লিনআপ কাজ" +} diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json new file mode 100644 index 00000000..1e7279be --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ca.json @@ -0,0 +1,141 @@ +{ + "Albums": "Àlbums", + "AppDeviceValues": "Aplicació: {0}, Dispositiu: {1}", + "Application": "Aplicació", + "Artists": "Artistes", + "AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament", + "Books": "Llibres", + "CameraImageUploadedFrom": "S'ha pujat una nova imatge de càmera des de {0}", + "Channels": "Canals", + "ChapterNameValue": "Capítol {0}", + "Collections": "Col·leccions", + "DeviceOfflineWithName": "{0} s'ha desconnectat", + "DeviceOnlineWithName": "{0} està connectat", + "FailedLoginAttemptWithUserName": "Intent de connexió fallit des de {0}", + "Favorites": "Preferits", + "Folders": "Directoris", + "Genres": "Gèneres", + "HeaderAlbumArtists": "Artistes de l'àlbum", + "HeaderContinueWatching": "Continueu mirant", + "HeaderFavoriteAlbums": "Àlbums preferits", + "HeaderFavoriteArtists": "Artistes preferits", + "HeaderFavoriteEpisodes": "Episodis preferits", + "HeaderFavoriteShows": "Sèries preferides", + "HeaderFavoriteSongs": "Cançons preferides", + "HeaderLiveTV": "TV en directe", + "HeaderNextUp": "A continuació", + "HeaderRecordingGroups": "Grups musicals", + "HomeVideos": "Vídeos domèstics", + "Inherit": "Heretat", + "ItemAddedWithName": "{0} s'ha afegit a la mediateca", + "ItemRemovedWithName": "{0} s'ha eliminat de la mediateca", + "LabelIpAddressValue": "Adreça IP: {0}", + "LabelRunningTimeValue": "Temps en marxa: {0}", + "Latest": "Darrers", + "MessageApplicationUpdated": "El servidor de Jellyfin ha estat actualitzat", + "MessageApplicationUpdatedTo": "El servidor de Jellyfin ha estat actualitzat a {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "La secció {0} de la configuració del servidor ha estat actualitzada", + "MessageServerConfigurationUpdated": "S'ha actualitzat la configuració del servidor", + "MixedContent": "Contingut barrejat", + "Movies": "Pel·lícules", + "Music": "Música", + "MusicVideos": "Videoclips", + "NameInstallFailed": "{0} instal·lació fallida", + "NameSeasonNumber": "Temporada {0}", + "NameSeasonUnknown": "Temporada desconeguda", + "NewVersionIsAvailable": "Hi ha disponible una versió nova del servidor de Jellyfin per a la descàrrega.", + "NotificationOptionApplicationUpdateAvailable": "Actualització de l'aplicatiu disponible", + "NotificationOptionApplicationUpdateInstalled": "Actualització de l'aplicatiu instal·lada", + "NotificationOptionAudioPlayback": "Reproducció d'àudio iniciada", + "NotificationOptionAudioPlaybackStopped": "Reproducció d'àudio aturada", + "NotificationOptionCameraImageUploaded": "Imatge de càmera pujada", + "NotificationOptionInstallationFailed": "Instal·lació fallida", + "NotificationOptionNewLibraryContent": "Nou contingut afegit", + "NotificationOptionPluginError": "Un complement ha fallat", + "NotificationOptionPluginInstalled": "Complement instal·lat", + "NotificationOptionPluginUninstalled": "Complement desinstal·lat", + "NotificationOptionPluginUpdateInstalled": "Actualització del complement instal·lada", + "NotificationOptionServerRestartRequired": "El servidor s'ha de reiniciar", + "NotificationOptionTaskFailed": "Tasca programada fallida", + "NotificationOptionUserLockedOut": "Usuari expulsat", + "NotificationOptionVideoPlayback": "Reproducció de vídeo iniciada", + "NotificationOptionVideoPlaybackStopped": "Reproducció de vídeo aturada", + "Photos": "Fotos", + "Playlists": "Llistes de reproducció", + "Plugin": "Complement", + "PluginInstalledWithName": "{0} ha estat instal·lat", + "PluginUninstalledWithName": "S'ha instal·lat {0}", + "PluginUpdatedWithName": "S'ha actualitzat {0}", + "ProviderValue": "Proveïdor: {0}", + "ScheduledTaskFailedWithName": "{0} ha fallat", + "ScheduledTaskStartedWithName": "S'ha iniciat {0}", + "ServerNameNeedsToBeRestarted": "S'ha de reiniciar {0}", + "Shows": "Sèries", + "Songs": "Cançons", + "StartupEmbyServerIsLoading": "El servidor de Jellyfin s'està carregant. Proveu-ho de nou en una estona.", + "SubtitleDownloadFailureFromForItem": "Els subtítols per a {1} no s'han pogut baixar de {0}", + "Sync": "Sincronitza", + "System": "Sistema", + "TvShows": "Sèries de TV", + "User": "Usuari", + "UserCreatedWithName": "S'ha creat l'usuari {0}", + "UserDeletedWithName": "S'ha eliminat l'usuari {0}", + "UserDownloadingItemWithValues": "{0} està descarregant {1}", + "UserLockedOutWithName": "S'ha expulsat a l'usuari {0}", + "UserOfflineFromDevice": "{0} s'ha desconnectat de {1}", + "UserOnlineFromDevice": "{0} està connectat des de {1}", + "UserPasswordChangedWithName": "S'ha canviat la contrasenya per a l'usuari {0}", + "UserPolicyUpdatedWithName": "La política d'usuari s'ha actualitzat per a {0}", + "UserStartedPlayingItemWithValues": "{0} ha començat a reproduir {1} a {2}", + "UserStoppedPlayingItemWithValues": "{0} ha parat de reproduir {1} a {2}", + "ValueHasBeenAddedToLibrary": "S'ha afegit {0} a la mediateca", + "ValueSpecialEpisodeName": "Especial - {0}", + "VersionNumber": "Versió {0}", + "TaskDownloadMissingSubtitlesDescription": "Cerca a internet els subtítols que faltin a partir de la configuració de metadades.", + "TaskDownloadMissingSubtitles": "Descàrrega dels subtítols que faltin", + "TaskRefreshChannelsDescription": "Actualitza la informació dels canals per internet.", + "TaskRefreshChannels": "Actualitza els canals", + "TaskCleanTranscodeDescription": "Elimina els fitxers de transcodificacions que tinguin més d'un dia.", + "TaskCleanTranscode": "Neteja de les transcodificacions", + "TaskUpdatePluginsDescription": "Descarrega i instal·la els complements que estiguin configurats per a actualitzar-se automàticament.", + "TaskUpdatePlugins": "Actualització dels complements", + "TaskRefreshPeopleDescription": "Actualització de les metadades dels actors i directors de la mediateca.", + "TaskRefreshPeople": "Actualització de les persones", + "TaskCleanLogsDescription": "Esborra els registres que tinguin més de {0} dies.", + "TaskCleanLogs": "Neteja dels registres", + "TaskRefreshLibraryDescription": "Escaneja les mediateques, a la cerca de fitxers nous i refresca les metadades.", + "TaskRefreshLibrary": "Escaneja la mediateca", + "TaskRefreshChapterImagesDescription": "Creació de les miniatures dels vídeos que tinguin capítols.", + "TaskRefreshChapterImages": "Extracció de les imatges dels capítols", + "TaskCleanCacheDescription": "Eliminació de la memòria cau no necessària per al servidor.", + "TaskCleanCache": "Eliminació de la memòria cau", + "TasksChannelsCategory": "Canals per internet", + "TasksApplicationCategory": "Aplicatiu", + "TasksLibraryCategory": "Mediateca", + "TasksMaintenanceCategory": "Manteniment", + "TaskCleanActivityLogDescription": "Eliminació de les entrades del registre d'activitats més antigues que l'antiguitat configurada.", + "TaskCleanActivityLog": "Buidatge del registre d'activitat", + "Undefined": "Indefinit", + "Forced": "Forçat", + "Default": "Per defecte", + "TaskOptimizeDatabaseDescription": "Compacta la base de dades i trunca l'espai lliure. Executar aquesta tasca després d’escanejar la mediateca o fer d'altres canvis que impliquin modificacions a la base de dades pot millorar el rendiment.", + "TaskOptimizeDatabase": "Optimització de la base de dades", + "TaskKeyframeExtractorDescription": "Extracció de fotogrames clau dels fitxers de vídeo per a crear llistes de reproducció HLS més precises. Aquesta tasca pot allargar-se molt en el temps.", + "TaskKeyframeExtractor": "Extracció de fotogrames clau", + "External": "Extern", + "HearingImpaired": "Discapacitat auditiva", + "TaskRefreshTrickplayImages": "Generació d'imatges de previsualització", + "TaskRefreshTrickplayImagesDescription": "Creació d'imatges de previsualització per a vídeos en les mediateques habilitades.", + "TaskCleanCollectionsAndPlaylistsDescription": "Esborra elements de col·leccions i llistes de reproducció que ja no existeixen.", + "TaskCleanCollectionsAndPlaylists": "Neteja de les col·leccions i llistes de reproducció", + "TaskAudioNormalization": "Estabilització de l'àudio", + "TaskAudioNormalizationDescription": "Escaneja els fitxer per a obtenir dades de normalització de l'àudio.", + "TaskDownloadMissingLyricsDescription": "Descàrrega de les lletres de les cançons", + "TaskDownloadMissingLyrics": "Descàrrega de les lletres que faltin", + "TaskExtractMediaSegments": "Escaneig de segments multimèdia", + "TaskExtractMediaSegmentsDescription": "Extreu o obté segments multimèdia usant els connectors MediaSegment activats.", + "TaskMoveTrickplayImages": "Migració de la ubicació de la imatge de previsualització", + "TaskMoveTrickplayImagesDescription": "Mou els fitxers existents d'imatges de previsualització segons la configuració de la mediateca.", + "CleanupUserDataTaskDescription": "Neteja totes les dades d'usuari (estat de la visualització, estat dels preferits, etc.) del contingut multimèdia que no ha estat present durant almenys 90 dies.", + "CleanupUserDataTask": "Tasca de neteja de dades d'usuari" +} diff --git a/Emby.Server.Implementations/Localization/Core/chr.json b/Emby.Server.Implementations/Localization/Core/chr.json new file mode 100644 index 00000000..85d1f4c8 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/chr.json @@ -0,0 +1,52 @@ +{ + "ChapterNameValue": "Didanedi {0}", + "HeaderAlbumArtists": "Didanidanolisgisgi", + "HeaderFavoriteAlbums": "Dvganidi didanidisgisgi", + "HeaderLiveTV": "Anigadi didanidisgosgi", + "HeaderRecordingGroups": "Didanisquodiisgisgi", + "HomeVideos": "Diganadi dinagadisgisgi", + "Inherit": "Anigwe", + "MessageApplicationUpdatedTo": "Tsenigwidinonvhi Jellyfin Server tsadanidigwe anigadi {0}", + "MixedContent": "Ganinidi dininoladisgisgi", + "Movies": "Anidvnisgisgi", + "MusicVideos": "Danodisgisgi didanidisgosgi", + "NotificationOptionAudioPlayback": "Didanidigwe diganuyisgisgi anigadi", + "NotificationOptionInstallationFailed": "Diudvdi anadvnatisgisgi", + "NotificationOptionPluginUninstalled": "Ditsigvhnidv anawvdisgisgi", + "Albums": "Anigawidaniyv", + "Application": "Didanvyi", + "Artists": "Dinidaniyi", + "AuthenticationSucceededWithUserName": "{0} Sesoquonisdi nagadani", + "Books": "Didanedi", + "CameraImageUploadedFrom": "Anigawidaniyv nasgi didagwalanvyi {0}", + "Channels": "Diganadasgi", + "Collections": "Diganadisgi", + "Default": "Dinadi", + "DeviceOfflineWithName": "{0} Aniyvolehvi nasgi", + "External": "Amohdi", + "Favorites": "Nvdayelvdisgi", + "Folders": "Didanididisgi", + "Forced": "Ganedi", + "Genres": "Diganadisgi", + "HeaderContinueWatching": "Uwoditsu asdanidisgisgi", + "HeaderFavoriteArtists": "Dvganidi dinidanolisgisgi", + "HeaderFavoriteEpisodes": "Dvganidi didanidilisgadisgisgi", + "HeaderFavoriteShows": "Dvganidi didanididanolisgisgi)", + "HeaderFavoriteSongs": "Dvganidi danodisgisgi", + "HeaderNextUp": "Anidvli uwodoli", + "HearingImpaired": "Anitsunidi talunidisgisgi", + "ItemAddedWithName": "{0} Dinigwe anididanidisgi", + "Latest": "Uwodoli", + "MessageApplicationUpdated": "Tsenigwidinonvhi Jellyfin Server tsadanidigwe", + "MessageServerConfigurationUpdated": "Sedanidvdi anigadi diganidinonvhi", + "Music": "Danodisgisgi", + "NameSeasonUnknown": "Tsunita anidvdisgi", + "NewVersionIsAvailable": "Danodigwe anigadi Jellyfin Server tsadanidigwe adisdi uwodvdi diganidinonvhi.", + "NotificationOptionApplicationUpdateAvailable": "Disisdi tsadanidigwe udvdi", + "NotificationOptionApplicationUpdateInstalled": "Disisdi tsadanidigwe digawvdi", + "NotificationOptionAudioPlaybackStopped": "Didanidigwe diganuyisgisgi digawvdi", + "NotificationOptionCameraImageUploaded": "Asdayi adininisgisgi diganuyisgisgi", + "NotificationOptionNewLibraryContent": "Danodisgisgi anigadi digawvdi", + "NotificationOptionPluginError": "Ditsigvhnidv anadvnatisgisgi", + "NotificationOptionPluginInstalled": "Ditsigvhnidv digawvdi" +} diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json new file mode 100644 index 00000000..4d247704 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -0,0 +1,141 @@ +{ + "Albums": "Alba", + "AppDeviceValues": "Aplikace: {0}, Zařízení: {1}", + "Application": "Aplikace", + "Artists": "Umělci", + "AuthenticationSucceededWithUserName": "{0} úspěšně ověřen", + "Books": "Knihy", + "CameraImageUploadedFrom": "Z {0} byla nahrána nová fotografie z fotoaparátu", + "Channels": "Kanály", + "ChapterNameValue": "Kapitola {0}", + "Collections": "Kolekce", + "DeviceOfflineWithName": "{0} se odpojil", + "DeviceOnlineWithName": "{0} je připojen", + "FailedLoginAttemptWithUserName": "Neúspěšný pokus o přihlášení z {0}", + "Favorites": "Oblíbené", + "Folders": "Složky", + "Genres": "Žánry", + "HeaderAlbumArtists": "Umělci alba", + "HeaderContinueWatching": "Pokračovat ve sledování", + "HeaderFavoriteAlbums": "Oblíbená alba", + "HeaderFavoriteArtists": "Oblíbení interpreti", + "HeaderFavoriteEpisodes": "Oblíbené epizody", + "HeaderFavoriteShows": "Oblíbené seriály", + "HeaderFavoriteSongs": "Oblíbená hudba", + "HeaderLiveTV": "TV vysílání", + "HeaderNextUp": "Další díly", + "HeaderRecordingGroups": "Skupiny nahrávek", + "HomeVideos": "Domácí videa", + "Inherit": "Zdědit", + "ItemAddedWithName": "{0} byl přidán do knihovny", + "ItemRemovedWithName": "{0} byl odstraněn z knihovny", + "LabelIpAddressValue": "IP adresa: {0}", + "LabelRunningTimeValue": "Délka média: {0}", + "Latest": "Nejnovější", + "MessageApplicationUpdated": "Jellyfin Server byl aktualizován", + "MessageApplicationUpdatedTo": "Jellyfin server byl aktualizován na verzi {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Konfigurace sekce {0} na serveru byla aktualizována", + "MessageServerConfigurationUpdated": "Konfigurace serveru aktualizována", + "MixedContent": "Smíšený obsah", + "Movies": "Filmy", + "Music": "Hudba", + "MusicVideos": "Hudební videa", + "NameInstallFailed": "Instalace {0} selhala", + "NameSeasonNumber": "Sezóna {0}", + "NameSeasonUnknown": "Neznámá sezóna", + "NewVersionIsAvailable": "Nová verze Jellyfin serveru je k dispozici ke stažení.", + "NotificationOptionApplicationUpdateAvailable": "Dostupná aktualizace aplikace", + "NotificationOptionApplicationUpdateInstalled": "Aktualizace aplikace instalována", + "NotificationOptionAudioPlayback": "Přehrávání audia zahájeno", + "NotificationOptionAudioPlaybackStopped": "Přehrávání audia ukončeno", + "NotificationOptionCameraImageUploaded": "Kamerový záznam nahrán", + "NotificationOptionInstallationFailed": "Chyba instalace", + "NotificationOptionNewLibraryContent": "Přidán nový obsah", + "NotificationOptionPluginError": "Chyba zásuvného modulu", + "NotificationOptionPluginInstalled": "Zásuvný modul instalován", + "NotificationOptionPluginUninstalled": "Zásuvný modul odstraněn", + "NotificationOptionPluginUpdateInstalled": "Aktualizace zásuvného modulu instalována", + "NotificationOptionServerRestartRequired": "Je vyžadován restart serveru", + "NotificationOptionTaskFailed": "Chyba naplánované úlohy", + "NotificationOptionUserLockedOut": "Uživatel uzamčen", + "NotificationOptionVideoPlayback": "Přehrávání videa zahájeno", + "NotificationOptionVideoPlaybackStopped": "Přehrávání videa ukončeno", + "Photos": "Fotky", + "Playlists": "Seznamy skladeb", + "Plugin": "Zásuvný modul", + "PluginInstalledWithName": "{0} byl nainstalován", + "PluginUninstalledWithName": "{0} byl odinstalován", + "PluginUpdatedWithName": "{0} byl aktualizován", + "ProviderValue": "Poskytl: {0}", + "ScheduledTaskFailedWithName": "{0} selhalo", + "ScheduledTaskStartedWithName": "{0} zahájeno", + "ServerNameNeedsToBeRestarted": "{0} vyžaduje restart", + "Shows": "Seriály", + "Songs": "Skladby", + "StartupEmbyServerIsLoading": "Jellyfin Server je spouštěn. Zkuste to prosím v brzké době znovu.", + "SubtitleDownloadFailureFromForItem": "Stažení titulků pro {1} z {0} selhalo", + "Sync": "Synchronizace", + "System": "Systém", + "TvShows": "Seriály", + "User": "Uživatel", + "UserCreatedWithName": "Uživatel {0} byl vytvořen", + "UserDeletedWithName": "Uživatel {0} byl smazán", + "UserDownloadingItemWithValues": "{0} stahuje {1}", + "UserLockedOutWithName": "Uživatel {0} byl odemčen", + "UserOfflineFromDevice": "{0} se odpojil ze zařízení {1}", + "UserOnlineFromDevice": "{0} se připojil ze zařízení {1}", + "UserPasswordChangedWithName": "Provedena změna hesla pro uživatele {0}", + "UserPolicyUpdatedWithName": "Zásady uživatele pro {0} byly aktualizovány", + "UserStartedPlayingItemWithValues": "{0} spustil přehrávání {1}", + "UserStoppedPlayingItemWithValues": "{0} zastavil přehrávání {1}", + "ValueHasBeenAddedToLibrary": "{0} byl přidán do vaší knihovny médií", + "ValueSpecialEpisodeName": "Speciál - {0}", + "VersionNumber": "Verze {0}", + "TaskDownloadMissingSubtitlesDescription": "Vyhledá na internetu chybějící titulky na základě nastavení metadat.", + "TaskDownloadMissingSubtitles": "Stáhnout chybějící titulky", + "TaskRefreshChannelsDescription": "Obnoví informace o internetových kanálech.", + "TaskRefreshChannels": "Obnovit kanály", + "TaskCleanTranscodeDescription": "Odstraní více než 1 den staré transkódované soubory.", + "TaskCleanTranscode": "Vyčistit adresář s transkódovaným obsahem", + "TaskUpdatePluginsDescription": "Stáhne a nainstaluje aktualizace zásuvných modulů, které mají nastavenou automatickou aktualizaci.", + "TaskUpdatePlugins": "Aktualizovat zásuvné moduly", + "TaskRefreshPeopleDescription": "Aktualizuje metadata umělců a režisérů ve Vaší knihovně médií.", + "TaskRefreshPeople": "Obnovit umělce", + "TaskCleanLogsDescription": "Odstraní soubory protokolu, které jsou starší více než {0} dní.", + "TaskCleanLogs": "Vyčistit adresář se souborem protokolu", + "TaskRefreshLibraryDescription": "Prohledá Vaši knihovnu médií zda neobsahuje nové soubory a obnoví metadatada.", + "TaskRefreshLibrary": "Prohledat knihovnu médií", + "TaskRefreshChapterImagesDescription": "Vytvoří náhledy videí, které obsahují kapitoly.", + "TaskRefreshChapterImages": "Extrahovat obrázky kapitol", + "TaskCleanCacheDescription": "Odstraní soubory mezipaměti, které systém již nebude potřebovat.", + "TaskCleanCache": "Vyčistit složku s mezipamětí", + "TasksChannelsCategory": "Internetové kanály", + "TasksApplicationCategory": "Aplikace", + "TasksLibraryCategory": "Knihovna", + "TasksMaintenanceCategory": "Údržba", + "TaskCleanActivityLogDescription": "Smazat záznamy o aktivitě, které jsou starší než zadaná doba.", + "TaskCleanActivityLog": "Smazat záznam aktivity", + "Undefined": "Nedefinované", + "Forced": "Vynucené", + "Default": "Výchozí", + "TaskOptimizeDatabaseDescription": "Zmenší databázi a odstraní prázdné místo. Spuštění této úlohy po skenování knihovny či jiných změnách databáze může zlepšit výkon.", + "TaskOptimizeDatabase": "Optimalizovat databázi", + "TaskKeyframeExtractorDescription": "Vytahuje klíčové snímky ze souborů videa za účelem vytváření přesnějších seznamů přehrávání HLS. Tento úkol může trvat velmi dlouho.", + "TaskKeyframeExtractor": "Vytahovač klíčových snímků", + "External": "Externí", + "HearingImpaired": "Sluchově postižení", + "TaskRefreshTrickplayImages": "Generovat obrázky pro Trickplay", + "TaskRefreshTrickplayImagesDescription": "Obrázky Trickplay se používají k zobrazení náhledů u videí v knihovnách, kde je to povoleno.", + "TaskCleanCollectionsAndPlaylists": "Pročistit kolekce a seznamy přehrávání", + "TaskCleanCollectionsAndPlaylistsDescription": "Odstraní neexistující položky z kolekcí a seznamů přehrávání.", + "TaskAudioNormalization": "Normalizace zvuku", + "TaskAudioNormalizationDescription": "Skenovat soubory za účelem normalizace zvuku.", + "TaskDownloadMissingLyrics": "Stáhnout chybějící texty k písni", + "TaskDownloadMissingLyricsDescription": "Stáhne texty k písni", + "TaskExtractMediaSegments": "Skenování segmentů médií", + "TaskExtractMediaSegmentsDescription": "Extrahuje či získá segmenty médií pomocí zásuvných modulů MediaSegment.", + "TaskMoveTrickplayImages": "Přesunout úložiště obrázků Trickplay", + "TaskMoveTrickplayImagesDescription": "Přesune existující soubory Trickplay podle nastavení knihovny.", + "CleanupUserDataTaskDescription": "Odstraní všechna uživatelská data (stav zhlédnutí, oblíbené atd.) z médií, které již neexistují více než 90 dní.", + "CleanupUserDataTask": "Pročistit uživatelská data" +} diff --git a/Emby.Server.Implementations/Localization/Core/cy.json b/Emby.Server.Implementations/Localization/Core/cy.json new file mode 100644 index 00000000..d9ebd13f --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/cy.json @@ -0,0 +1,136 @@ +{ + "DeviceOnlineWithName": "Mae {0} wedi'i gysylltu", + "DeviceOfflineWithName": "Mae {0} wedi datgysylltu", + "Default": "Diofyn", + "Collections": "Casgliadau", + "ChapterNameValue": "Pennod {0}", + "Channels": "Sianeli", + "CameraImageUploadedFrom": "Mae delwedd camera newydd wedi'i lanlwytho o {0}", + "Books": "Llyfrau", + "AuthenticationSucceededWithUserName": "{0} wedi’i ddilysu’n llwyddiannus", + "Artists": "Crewyr", + "AppDeviceValues": "Ap: {0}, Dyfais: {1}", + "Albums": "Albwmau", + "Genres": "Genres", + "Folders": "Ffolderi", + "Favorites": "Ffefrynnau", + "LabelRunningTimeValue": "Amser rhedeg: {0}", + "TaskOptimizeDatabase": "Optimeiddio cronfa ddata", + "TaskRefreshChannels": "Adnewyddu Sianeli", + "TaskRefreshPeople": "Adnewyddu Pobl", + "TasksChannelsCategory": "Sianeli Internet", + "VersionNumber": "Fersiwn {0}", + "ScheduledTaskStartedWithName": "{0} wedi dechrau", + "ScheduledTaskFailedWithName": "{0} wedi methu", + "ProviderValue": "Darparwr: {0}", + "NotificationOptionInstallationFailed": "Fethu Gosod", + "NameSeasonUnknown": "Tymor Anhysbys", + "NameSeasonNumber": "Tymor {0}", + "MusicVideos": "Fideos Cerddoriaeth", + "MixedContent": "Cynnwys amrywiol", + "HomeVideos": "Genres", + "HeaderNextUp": "Nesaf i Fyny", + "HeaderFavoriteArtists": "Ffefryn Artistiaid", + "HeaderFavoriteAlbums": "Ffefryn Albwmau", + "HeaderContinueWatching": "Parhewch i Wylio", + "TasksApplicationCategory": "Rhaglen", + "TasksLibraryCategory": "Llyfrgell", + "TasksMaintenanceCategory": "Cynnal a Chadw", + "System": "System", + "Plugin": "Ategyn", + "Music": "Cerddoriaeth", + "Latest": "Diweddaraf", + "Inherit": "Etifeddu", + "Forced": "Orfodi", + "Application": "Rhaglen", + "HeaderAlbumArtists": "Artistiaid albwm", + "Sync": "Cysoni", + "Songs": "Caneuon", + "Shows": "Rhaglenni", + "Playlists": "Rhestri Chwarae", + "Photos": "Lluniau", + "ValueSpecialEpisodeName": "Arbennig - {0}", + "Movies": "Ffilmiau", + "Undefined": "Heb ddiffiniad", + "TvShows": "Rhaglenni teledu", + "HeaderLiveTV": "Teledu Byw", + "User": "Defnyddiwr", + "TaskCleanLogsDescription": "Dileu ffeiliau log sy'n fwy na {0} diwrnod oed.", + "TaskCleanLogs": "Glanhau ffolder log", + "TaskRefreshLibraryDescription": "Sganio'ch llyfrgell gyfryngau am ffeiliau newydd ac yn adnewyddu metaddata.", + "TaskRefreshLibrary": "Sganwich Llyfrgell Cyfryngau", + "TaskCleanActivityLogDescription": "Yn dileu cofnodion log gweithgaredd sy'n hŷn na'r oedran a nodwyd.", + "TaskCleanActivityLog": "Glanhau Log Gweithgaredd", + "SubtitleDownloadFailureFromForItem": "Methodd is-deitlau lawrlwytho o {0} ar gyfer {1}", + "NotificationOptionPluginError": "Methodd ategyn", + "NotificationOptionAudioPlaybackStopped": "Stopiwyd chwarae sain", + "NotificationOptionAudioPlayback": "Dechreuwyd chwarae sain", + "MessageServerConfigurationUpdated": "Mae gosodiadau gweinydd wedi'i ddiweddaru", + "MessageNamedServerConfigurationUpdatedWithValue": "Mae adran gosodiadau gweinydd {0} wedi'i diweddaru", + "FailedLoginAttemptWithUserName": "Cais mewngofnodi wedi methu o {0}", + "ValueHasBeenAddedToLibrary": "{0} wedi'i hychwanegu at eich llyfrgell gyfryngau", + "UserStoppedPlayingItemWithValues": "{0} wedi gorffen chwarae {1} ar {2}", + "UserStartedPlayingItemWithValues": "{0} yn chwarae {1} ar {2}", + "UserPolicyUpdatedWithName": "Polisi defnyddiwr wedi'i newid ar gyfer {0}", + "UserPasswordChangedWithName": "Cyfrinair wedi'i newid ar gyfer defnyddiwr {0}", + "UserOnlineFromDevice": "Mae {0} ar-lein o {1}", + "UserOfflineFromDevice": "Mae {0} wedi datgysylltu o {1}", + "UserLockedOutWithName": "Mae defnyddiwr {0} wedi'i gloi allan", + "UserDownloadingItemWithValues": "Mae {0} yn lawrlwytho {1}", + "UserDeletedWithName": "Defnyddiwr {0} wedi'i ddileu", + "UserCreatedWithName": "Defnyddiwr {0} wedi'i greu", + "StartupEmbyServerIsLoading": "Gweinydd Jellyfin yn llwytho. Triwch eto mewn ychydig.", + "ServerNameNeedsToBeRestarted": "Mae angen ailddechrau {0}", + "PluginUpdatedWithName": "{0} wedi'i ddiweddaru", + "PluginUninstalledWithName": "{0} wedi'i ddadosod", + "PluginInstalledWithName": "{0} wedi'i osod", + "NotificationOptionVideoPlaybackStopped": "Stopiwyd chwarae fideo", + "NotificationOptionVideoPlayback": "Dechreuwyd chwarae fideo", + "NotificationOptionUserLockedOut": "Defnyddiwr wedi'i gloi allan", + "NotificationOptionTaskFailed": "Methwyd cyflawni y dasg a drefnwyd", + "NotificationOptionServerRestartRequired": "Mae angen ailgychwyn y gweinydd", + "NotificationOptionPluginUpdateInstalled": "Diweddariad ategyn wedi'i osod", + "NotificationOptionPluginUninstalled": "Ategyn wedi'i ddadosod", + "NotificationOptionPluginInstalled": "Ategyn wedi'i osod", + "NotificationOptionNewLibraryContent": "Cynnwys newydd ar gael", + "NotificationOptionCameraImageUploaded": "Llun camera wedi'i huwchlwytho", + "NotificationOptionApplicationUpdateInstalled": "Diweddariad ap wedi'i osod", + "NotificationOptionApplicationUpdateAvailable": "Diweddariad ap ar gael", + "NewVersionIsAvailable": "Mae fersiwn diweddarach o'r gweinydd Jellyfin ar gael.", + "NameInstallFailed": "Gosodiad {0} wedi methu", + "MessageApplicationUpdatedTo": "Gweinydd Jellyfin wedi'i ddiweddaru i {0}", + "MessageApplicationUpdated": "Gweinydd Jellyfin wedi'i ddiweddaru", + "LabelIpAddressValue": "Cyfeiriad IP: {0}", + "ItemRemovedWithName": "{0} wedi'i dynnu o'r llyfrgell", + "ItemAddedWithName": "{0} wedi'i adio i'r llyfrgell", + "HeaderRecordingGroups": "Grwpiau Recordio", + "HeaderFavoriteSongs": "Ffefryn Ganeuon", + "HeaderFavoriteShows": "Ffefryn Shoeau", + "HeaderFavoriteEpisodes": "Ffefryn Rhaglenni", + "TaskDownloadMissingSubtitlesDescription": "Chwilio'r rhyngrwyd am is-deitlau coll yn seiliedig ar gosodiadau metaddata.", + "TaskDownloadMissingSubtitles": "Lawrlwytho isdeitlau coll", + "TaskCleanTranscodeDescription": "Dileu ffeiliau trawsgodio fwy nag un diwrnod oed.", + "External": "Allanol", + "TaskKeyframeExtractorDescription": "Echdynnu fframiau o ffeiliau fideo i greu rhestrau chwarae HLS mwy manwl gywir. Gall y dasg hon redeg am amser hir.", + "TaskKeyframeExtractor": "Echdynnwr ffram-allwedd", + "TaskOptimizeDatabaseDescription": "Crynhoi cronfa ddata ac yn cwtogi'r gofod rhydd. Gallai rhedeg y dasg hon ar ôl sganio'r llyfrgell neu wneud newidiadau eraill sy'n addasu'r cronfa ddata wella perfformiad.", + "TaskRefreshChannelsDescription": "Diweddaru gwybodaeth sianeli rhyngrwyd.", + "TaskCleanTranscode": "Gwaghau Ffolder Trawsgodau", + "TaskUpdatePluginsDescription": "Lawrlwytho ac yn gosod diweddariadau ar gyfer ategion sydd wedi'u gosod i'w diweddaru'n awtomatig.", + "TaskUpdatePlugins": "Diweddaru Ategion", + "TaskRefreshPeopleDescription": "Yn diweddaru metaddata ar gyfer actorion a chyfarwyddwyr yn eich llyfrgell gyfryngau.", + "TaskRefreshChapterImagesDescription": "Creu mân-luniau ar gyfer fideos sydd â phenodau.", + "TaskRefreshChapterImages": "Echdynnu Lluniau Pennod", + "TaskCleanCacheDescription": "Dileu ffeiliau cache nad oes eu hangen ar y system mwyach.", + "TaskCleanCache": "Gwaghau Ffolder Cache", + "HearingImpaired": "Nam ar y clyw", + "TaskAudioNormalization": "Gwastatau Sain", + "TaskAudioNormalizationDescription": "Yn sganio ffeiliau am ddata gwastatau sain.", + "TaskRefreshTrickplayImages": "Creuwch lluniau Trickplay", + "TaskRefreshTrickplayImagesDescription": "Creu rhagolygon Trickplay ar gyfer fideos mewn llyfrgelloedd gweithredol.", + "TaskDownloadMissingLyrics": "Lawrlwytho geiriau coll", + "TaskDownloadMissingLyricsDescription": "Lawrlwytho geiriau caneuon", + "TaskCleanCollectionsAndPlaylists": "Glanhau casgliadau a rhestrau chwarae", + "TaskCleanCollectionsAndPlaylistsDescription": "Dileu eitemau o gasgliadau a rhestrau chwarae sydd ddim yn bodoli bellach.", + "TaskExtractMediaSegments": "Sganio Darnau Cyfryngau" +} diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json new file mode 100644 index 00000000..8b0d8745 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -0,0 +1,141 @@ +{ + "Albums": "Albummer", + "AppDeviceValues": "App: {0}, Enhed: {1}", + "Application": "Applikation", + "Artists": "Kunstnere", + "AuthenticationSucceededWithUserName": "{0} er logget ind", + "Books": "Bøger", + "CameraImageUploadedFrom": "Et nyt kamerabillede er blevet uploadet fra {0}", + "Channels": "Kanaler", + "ChapterNameValue": "Kapitel {0}", + "Collections": "Samlinger", + "DeviceOfflineWithName": "{0} har afbrudt forbindelsen", + "DeviceOnlineWithName": "{0} er forbundet", + "FailedLoginAttemptWithUserName": "Mislykket loginforsøg fra {0}", + "Favorites": "Favoritter", + "Folders": "Mapper", + "Genres": "Genrer", + "HeaderAlbumArtists": "Albumkunstnere", + "HeaderContinueWatching": "Fortsæt afspilning", + "HeaderFavoriteAlbums": "Favoritalbum", + "HeaderFavoriteArtists": "Favoritkunstnere", + "HeaderFavoriteEpisodes": "Yndlingsafsnit", + "HeaderFavoriteShows": "Yndlingsserier", + "HeaderFavoriteSongs": "Yndlingssange", + "HeaderLiveTV": "Live-TV", + "HeaderNextUp": "Næste", + "HeaderRecordingGroups": "Optagelsesgrupper", + "HomeVideos": "Hjemmevideoer", + "Inherit": "Nedarv", + "ItemAddedWithName": "{0} blev tilføjet til biblioteket", + "ItemRemovedWithName": "{0} blev fjernet fra biblioteket", + "LabelIpAddressValue": "IP-adresse: {0}", + "LabelRunningTimeValue": "Spilletid: {0}", + "Latest": "Seneste", + "MessageApplicationUpdated": "Jellyfin Server er blevet opdateret", + "MessageApplicationUpdatedTo": "Jellyfin Server er blevet opdateret til {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfiguration sektion {0} er blevet opdateret", + "MessageServerConfigurationUpdated": "Serverkonfigurationen er blevet opdateret", + "MixedContent": "Blandet indhold", + "Movies": "Film", + "Music": "Musik", + "MusicVideos": "Musikvideoer", + "NameInstallFailed": "{0} installationen mislykkedes", + "NameSeasonNumber": "Sæson {0}", + "NameSeasonUnknown": "Ukendt sæson", + "NewVersionIsAvailable": "En ny version af Jellyfin Server er tilgængelig.", + "NotificationOptionApplicationUpdateAvailable": "Opdatering til applikationen er tilgængelig", + "NotificationOptionApplicationUpdateInstalled": "Opdatering til applikationen blev installeret", + "NotificationOptionAudioPlayback": "Lydafspilning påbegyndt", + "NotificationOptionAudioPlaybackStopped": "Lydafspilning stoppet", + "NotificationOptionCameraImageUploaded": "Kamerabillede uploadet", + "NotificationOptionInstallationFailed": "Installationen mislykkedes", + "NotificationOptionNewLibraryContent": "Nyt indhold tilføjet", + "NotificationOptionPluginError": "Plugin-fejl", + "NotificationOptionPluginInstalled": "Plugin blev installeret", + "NotificationOptionPluginUninstalled": "Plugin blev afinstalleret", + "NotificationOptionPluginUpdateInstalled": "Opdatering til plugin blev installeret", + "NotificationOptionServerRestartRequired": "Genstart af serveren er påkrævet", + "NotificationOptionTaskFailed": "Planlagt opgave er fejlet", + "NotificationOptionUserLockedOut": "Bruger er låst ude", + "NotificationOptionVideoPlayback": "Videoafspilning påbegyndt", + "NotificationOptionVideoPlaybackStopped": "Videoafspilning blev stoppet", + "Photos": "Fotos", + "Playlists": "Afspilningslister", + "Plugin": "Plugin", + "PluginInstalledWithName": "{0} blev installeret", + "PluginUninstalledWithName": "{0} blev afinstalleret", + "PluginUpdatedWithName": "{0} blev opdateret", + "ProviderValue": "Udbyder: {0}", + "ScheduledTaskFailedWithName": "{0} mislykkedes", + "ScheduledTaskStartedWithName": "{0} påbegyndte", + "ServerNameNeedsToBeRestarted": "{0} skal genstartes", + "Shows": "Serier", + "Songs": "Sange", + "StartupEmbyServerIsLoading": "Jellyfin er i gang med at starte. Prøv igen om et øjeblik.", + "SubtitleDownloadFailureFromForItem": "Undertekster kunne ikke hentes fra {0} til {1}", + "Sync": "Synkroniser", + "System": "System", + "TvShows": "TV-serier", + "User": "Bruger", + "UserCreatedWithName": "Bruger {0} er blevet oprettet", + "UserDeletedWithName": "Brugeren {0} er nu slettet", + "UserDownloadingItemWithValues": "{0} henter {1}", + "UserLockedOutWithName": "Brugeren {0} er blevet låst ude", + "UserOfflineFromDevice": "{0} har afbrudt fra {1}", + "UserOnlineFromDevice": "{0} er online fra {1}", + "UserPasswordChangedWithName": "Adgangskode er ændret for brugeren {0}", + "UserPolicyUpdatedWithName": "Brugerpolitikken er blevet opdateret for {0}", + "UserStartedPlayingItemWithValues": "{0} afspiller {1} på {2}", + "UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1} på {2}", + "ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek", + "ValueSpecialEpisodeName": "Special - {0}", + "VersionNumber": "Version {0}", + "TaskDownloadMissingSubtitlesDescription": "Søger på internettet efter manglende undertekster baseret på metadata-konfigurationen.", + "TaskDownloadMissingSubtitles": "Hent manglende undertekster", + "TaskUpdatePluginsDescription": "Henter og installerer opdateringer for plugins, som er konfigurerede til at blive opdateret automatisk.", + "TaskUpdatePlugins": "Opdater plugins", + "TaskCleanLogsDescription": "Sletter log-filer som er mere end {0} dage gamle.", + "TaskCleanLogs": "Ryd log-mappe", + "TaskRefreshLibraryDescription": "Scanner dit mediebibliotek for nye filer og opdateret metadata.", + "TaskRefreshLibrary": "Scan mediebibliotek", + "TaskCleanCacheDescription": "Sletter cache-filer som systemet ikke længere bruger.", + "TaskCleanCache": "Ryd cache-mappe", + "TasksChannelsCategory": "Internetkanaler", + "TasksApplicationCategory": "Applikation", + "TasksLibraryCategory": "Bibliotek", + "TasksMaintenanceCategory": "Vedligeholdelse", + "TaskRefreshChapterImages": "Udtræk kapitelbilleder", + "TaskRefreshChapterImagesDescription": "Laver miniaturebilleder for videoer, der har kapitler.", + "TaskRefreshChannelsDescription": "Opdaterer information for internetkanaler.", + "TaskRefreshChannels": "Opdater kanaler", + "TaskCleanTranscodeDescription": "Fjerner omkodningsfiler, som er mere end 1 dag gamle.", + "TaskCleanTranscode": "Tøm omkodningsmappen", + "TaskRefreshPeople": "Opdater personer", + "TaskRefreshPeopleDescription": "Opdaterer metadata for skuespillere og instruktører i dit mediebibliotek.", + "TaskCleanActivityLogDescription": "Sletter linjer i aktivitetsloggen ældre end den konfigurerede alder.", + "TaskCleanActivityLog": "Ryd aktivitetslog", + "Undefined": "Udefineret", + "Forced": "Tvunget", + "Default": "Standard", + "TaskOptimizeDatabaseDescription": "Komprimerer databasen for at frigøre plads. Denne handling køres efter at have scannet mediebiblioteket, eller efter at have lavet ændringer til databasen.", + "TaskOptimizeDatabase": "Optimer database", + "TaskKeyframeExtractorDescription": "Udtrækker rammer fra videofiler for at lave mere præcise HLS-playlister. Denne opgave kan tage lang tid.", + "TaskKeyframeExtractor": "Udtræk nøglerammer", + "External": "Ekstern", + "HearingImpaired": "Hørehæmmet", + "TaskRefreshTrickplayImages": "Generer trickplay-billeder", + "TaskRefreshTrickplayImagesDescription": "Laver trickplay-billeder for videoer i aktiverede biblioteker.", + "TaskCleanCollectionsAndPlaylists": "Ryd op i samlinger og afspilningslister", + "TaskCleanCollectionsAndPlaylistsDescription": "Fjerner elementer fra samlinger og afspilningslister der ikke eksisterer længere.", + "TaskAudioNormalizationDescription": "Skanner filer for data vedrørende lydnormalisering.", + "TaskAudioNormalization": "Lydnormalisering", + "TaskDownloadMissingLyricsDescription": "Søger på internettet efter manglende sangtekster baseret på metadata-konfigurationen", + "TaskDownloadMissingLyrics": "Hent manglende sangtekster", + "TaskExtractMediaSegments": "Scan for mediesegmenter", + "TaskMoveTrickplayImages": "Migrer billedelokationer for trickplay-billeder", + "TaskMoveTrickplayImagesDescription": "Flyt eksisterende trickplay-billeder jævnfør biblioteksindstillinger.", + "TaskExtractMediaSegmentsDescription": "Udtrækker eller henter mediesegmenter fra plugins som understøtter MediaSegment.", + "CleanupUserDataTask": "Brugerdata oprydningsopgave", + "CleanupUserDataTaskDescription": "Rydder alle brugerdata (eks. visning- og favoritstatus) fra medier, der har været utilgængelige i mindst 90 dage." +} diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json new file mode 100644 index 00000000..e9a1630d --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -0,0 +1,141 @@ +{ + "Albums": "Alben", + "AppDeviceValues": "App: {0}, Gerät: {1}", + "Application": "Anwendung", + "Artists": "Interpreten", + "AuthenticationSucceededWithUserName": "{0} erfolgreich authentifiziert", + "Books": "Bücher", + "CameraImageUploadedFrom": "Ein neues Kamerabild wurde von {0} hochgeladen", + "Channels": "Kanäle", + "ChapterNameValue": "Kapitel {0}", + "Collections": "Sammlungen", + "DeviceOfflineWithName": "{0} ist offline", + "DeviceOnlineWithName": "{0} ist online", + "FailedLoginAttemptWithUserName": "Anmeldung von {0} fehlgeschlagen", + "Favorites": "Favoriten", + "Folders": "Verzeichnisse", + "Genres": "Genres", + "HeaderAlbumArtists": "Album-Interpreten", + "HeaderContinueWatching": "Weiterschauen", + "HeaderFavoriteAlbums": "Lieblingsalben", + "HeaderFavoriteArtists": "Lieblingsinterpreten", + "HeaderFavoriteEpisodes": "Lieblingsepisoden", + "HeaderFavoriteShows": "Lieblingsserien", + "HeaderFavoriteSongs": "Lieblingssongs", + "HeaderLiveTV": "Live TV", + "HeaderNextUp": "Als Nächstes", + "HeaderRecordingGroups": "Aufnahme-Gruppen", + "HomeVideos": "Heimvideos", + "Inherit": "Vererben", + "ItemAddedWithName": "{0} wurde der Bibliothek hinzugefügt", + "ItemRemovedWithName": "{0} wurde aus der Bibliothek entfernt", + "LabelIpAddressValue": "IP-Adresse: {0}", + "LabelRunningTimeValue": "Laufzeit: {0}", + "Latest": "Neueste", + "MessageApplicationUpdated": "Jellyfin-Server wurde aktualisiert", + "MessageApplicationUpdatedTo": "Jellyfin-Server wurde auf Version {0} aktualisiert", + "MessageNamedServerConfigurationUpdatedWithValue": "Der Server-Einstellungsbereich {0} wurde aktualisiert", + "MessageServerConfigurationUpdated": "Servereinstellungen wurden aktualisiert", + "MixedContent": "Gemischte Inhalte", + "Movies": "Filme", + "Music": "Musik", + "MusicVideos": "Musikvideos", + "NameInstallFailed": "Installation von {0} fehlgeschlagen", + "NameSeasonNumber": "Staffel {0}", + "NameSeasonUnknown": "Staffel unbekannt", + "NewVersionIsAvailable": "Eine neue Jellyfin-Serverversion steht zum Download bereit.", + "NotificationOptionApplicationUpdateAvailable": "Anwendungsaktualisierung verfügbar", + "NotificationOptionApplicationUpdateInstalled": "Anwendungsaktualisierung installiert", + "NotificationOptionAudioPlayback": "Audio wird abgespielt", + "NotificationOptionAudioPlaybackStopped": "Audiowiedergabe gestoppt", + "NotificationOptionCameraImageUploaded": "Foto hochgeladen", + "NotificationOptionInstallationFailed": "Installation fehlgeschlagen", + "NotificationOptionNewLibraryContent": "Neuer Inhalt hinzugefügt", + "NotificationOptionPluginError": "Plugin-Fehler", + "NotificationOptionPluginInstalled": "Plugin installiert", + "NotificationOptionPluginUninstalled": "Plugin deinstalliert", + "NotificationOptionPluginUpdateInstalled": "Pluginaktualisierung installiert", + "NotificationOptionServerRestartRequired": "Serverneustart notwendig", + "NotificationOptionTaskFailed": "Geplante Aufgabe fehlgeschlagen", + "NotificationOptionUserLockedOut": "Benutzer gesperrt", + "NotificationOptionVideoPlayback": "Video wird abgespielt", + "NotificationOptionVideoPlaybackStopped": "Videowiedergabe gestoppt", + "Photos": "Fotos", + "Playlists": "Wiedergabelisten", + "Plugin": "Plugin", + "PluginInstalledWithName": "{0} wurde installiert", + "PluginUninstalledWithName": "{0} wurde deinstalliert", + "PluginUpdatedWithName": "{0} wurde aktualisiert", + "ProviderValue": "Anbieter: {0}", + "ScheduledTaskFailedWithName": "{0} ist fehlgeschlagen", + "ScheduledTaskStartedWithName": "{0} wurde gestartet", + "ServerNameNeedsToBeRestarted": "{0} muss neu gestartet werden", + "Shows": "Serien", + "Songs": "Lieder", + "StartupEmbyServerIsLoading": "Jellyfin-Server lädt. Bitte versuche es gleich noch einmal.", + "SubtitleDownloadFailureFromForItem": "Untertitel von {0} für {1} konnten nicht heruntergeladen werden", + "Sync": "Synchronisation", + "System": "System", + "TvShows": "Serien", + "User": "Benutzer", + "UserCreatedWithName": "Benutzer {0} wurde erstellt", + "UserDeletedWithName": "Benutzer {0} wurde gelöscht", + "UserDownloadingItemWithValues": "{0} lädt {1} herunter", + "UserLockedOutWithName": "Benutzer {0} wurde gesperrt", + "UserOfflineFromDevice": "{0} wurde getrennt von {1}", + "UserOnlineFromDevice": "{0} ist online von {1}", + "UserPasswordChangedWithName": "Das Passwort für Benutzer {0} wurde geändert", + "UserPolicyUpdatedWithName": "Benutzerrichtlinie von {0} wurde aktualisiert", + "UserStartedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} gestartet", + "UserStoppedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} beendet", + "ValueHasBeenAddedToLibrary": "{0} wurde deiner Bibliothek hinzugefügt", + "ValueSpecialEpisodeName": "Extra – {0}", + "VersionNumber": "Version {0}", + "TaskDownloadMissingSubtitlesDescription": "Sucht im Internet basierend auf den Metadaten-Einstellungen nach fehlenden Untertiteln.", + "TaskDownloadMissingSubtitles": "Fehlende Untertitel herunterladen", + "TaskRefreshChannelsDescription": "Aktualisiert Internet-Kanal-Informationen.", + "TaskRefreshChannels": "Kanäle aktualisieren", + "TaskCleanTranscodeDescription": "Löscht Transkodierungsdateien, die älter als einen Tag sind.", + "TaskCleanTranscode": "Transkodierungsverzeichnis leeren", + "TaskUpdatePluginsDescription": "Lädt Updates für Plugins herunter, welche für automatische Updates konfiguriert sind und installiert diese.", + "TaskUpdatePlugins": "Plugins aktualisieren", + "TaskRefreshPeopleDescription": "Aktualisiert Metadaten für Schauspieler und Regisseure in deinen Bibliotheken.", + "TaskRefreshPeople": "Personen aktualisieren", + "TaskCleanLogsDescription": "Lösche Log-Dateien, die älter als {0} Tage sind.", + "TaskCleanLogs": "Protokollverzeichnis leeren", + "TaskRefreshLibraryDescription": "Durchsucht deine Medienbibliothek nach neuen Dateien und aktualisiert Metadaten.", + "TaskRefreshLibrary": "Medien-Bibliothek scannen", + "TaskRefreshChapterImagesDescription": "Erstellt Vorschaubilder für Videokapitel.", + "TaskRefreshChapterImages": "Kapitelvorschauen erstellen", + "TaskCleanCacheDescription": "Löscht Cache-Dateien, die vom System nicht mehr benötigt werden.", + "TaskCleanCache": "Cache-Verzeichnis leeren", + "TasksChannelsCategory": "Internet-Kanäle", + "TasksApplicationCategory": "Anwendung", + "TasksLibraryCategory": "Bibliothek", + "TasksMaintenanceCategory": "Wartung", + "TaskCleanActivityLogDescription": "Löscht Aktivitätsprotokolleinträge, die älter als das konfigurierte Alter sind.", + "TaskCleanActivityLog": "Aktivitätsverlauf bereinigen", + "Undefined": "Undefiniert", + "Forced": "Erzwungen", + "Default": "Standard", + "TaskOptimizeDatabaseDescription": "Komprimiert die Datenbank und trimmt den freien Speicherplatz. Die Ausführung dieser Aufgabe nach dem Scannen der Bibliothek oder nach anderen Änderungen, die Datenbankänderungen implizieren, kann die Leistung verbessern.", + "TaskOptimizeDatabase": "Datenbank optimieren", + "TaskKeyframeExtractorDescription": "Extrahiert Keyframes aus Videodateien, um präzisere HLS-Playlisten zu erzeugen. Dieser Vorgang kann sehr lange dauern.", + "TaskKeyframeExtractor": "Keyframe-Extraktor", + "External": "Extern", + "HearingImpaired": "Hörgeschädigt", + "TaskRefreshTrickplayImages": "Trickplay-Bilder generieren", + "TaskRefreshTrickplayImagesDescription": "Erstellt ein Trickplay-Vorschauen für Videos in aktivierten Bibliotheken.", + "TaskCleanCollectionsAndPlaylists": "Sammlungen und Playlisten aufräumen", + "TaskCleanCollectionsAndPlaylistsDescription": "Löscht nicht mehr vorhandene Einträge aus den Sammlungen und Playlisten.", + "TaskAudioNormalization": "Audio Normalisierung", + "TaskAudioNormalizationDescription": "Durchsucht Dateien nach Audionormalisierungsdaten.", + "TaskDownloadMissingLyricsDescription": "Lädt Songtexte herunter", + "TaskDownloadMissingLyrics": "Fehlende Songtexte herunterladen", + "TaskExtractMediaSegments": "Mediensegmente scannen", + "TaskExtractMediaSegmentsDescription": "Extrahiert oder empfängt Mediensegmente von Plugins die Mediensegmente nutzen.", + "TaskMoveTrickplayImages": "Verzeichnis für Trickplay-Bilder migrieren", + "TaskMoveTrickplayImagesDescription": "Trickplay-Bilder werden entsprechend der Bibliothekseinstellungen verschoben.", + "CleanupUserDataTask": "Aufgabe zur Bereinigung von Benutzerdaten", + "CleanupUserDataTaskDescription": "Löscht alle Benutzerdaten (Abspielstatus, Favoritenstatus, usw.) von Medien, die seit mindestens 90 Tagen nicht mehr vorhanden sind." +} diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json new file mode 100644 index 00000000..87362ff8 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/el.json @@ -0,0 +1,141 @@ +{ + "Albums": "Άλμπουμ", + "AppDeviceValues": "Εφαρμογή: {0}, Συσκευή: {1}", + "Application": "Εφαρμογή", + "Artists": "Καλλιτέχνες", + "AuthenticationSucceededWithUserName": "Ο χρήστης {0} επαληθεύτηκε επιτυχώς", + "Books": "Βιβλία", + "CameraImageUploadedFrom": "Μια νέα φωτογραφία φορτώθηκε από {0}", + "Channels": "Κανάλια", + "ChapterNameValue": "Κεφάλαιο {0}", + "Collections": "Συλλογές", + "DeviceOfflineWithName": "Ο/Η {0} αποσυνδέθηκε", + "DeviceOnlineWithName": "Ο/Η {0} συνδέθηκε", + "FailedLoginAttemptWithUserName": "Αποτυχία προσπάθειας σύνδεσης από {0}", + "Favorites": "Αγαπημένα", + "Folders": "Φάκελοι", + "Genres": "Είδη", + "HeaderAlbumArtists": "Καλλιτέχνες άλμπουμ", + "HeaderContinueWatching": "Συνεχίστε την παρακολούθηση", + "HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ", + "HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες", + "HeaderFavoriteEpisodes": "Αγαπημένα Επεισόδια", + "HeaderFavoriteShows": "Αγαπημένες Σειρές", + "HeaderFavoriteSongs": "Αγαπημένα Τραγούδια", + "HeaderLiveTV": "Ζωντανή Τηλεόραση", + "HeaderNextUp": "Επόμενο", + "HeaderRecordingGroups": "Ομάδες Ηχογράφησης", + "HomeVideos": "Προσωπικά Βίντεο", + "Inherit": "Κληρονόμηση", + "ItemAddedWithName": "Το {0} προστέθηκε στη βιβλιοθήκη", + "ItemRemovedWithName": "Το {0} διαγράφτηκε από τη βιβλιοθήκη", + "LabelIpAddressValue": "Διεύθυνση IP: {0}", + "LabelRunningTimeValue": "Διάρκεια: {0}", + "Latest": "Πρόσφατα", + "MessageApplicationUpdated": "Ο διακομιστής Jellyfin έχει ενημερωθεί", + "MessageApplicationUpdatedTo": "Ο διακομιστής Jellyfin αναβαθμίστηκε στην έκδοση {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Η ενότητα {0} ρύθμισης παραμέτρων του διακομιστή έχει ενημερωθεί", + "MessageServerConfigurationUpdated": "Η ρύθμιση παραμέτρων του διακομιστή έχει ενημερωθεί", + "MixedContent": "Ανάμεικτο Περιεχόμενο", + "Movies": "Ταινίες", + "Music": "Μουσική", + "MusicVideos": "Μουσικά Βίντεο", + "NameInstallFailed": "H εγκατάσταση του {0} απέτυχε", + "NameSeasonNumber": "Κύκλος {0}", + "NameSeasonUnknown": "Άγνωστος Κύκλος", + "NewVersionIsAvailable": "Μια νέα έκδοση του διακομιστή Jellyfin είναι διαθέσιμη για λήψη.", + "NotificationOptionApplicationUpdateAvailable": "Διαθέσιμη ενημερωμένη έκδοση εφαρμογής", + "NotificationOptionApplicationUpdateInstalled": "Η ενημέρωση εφαρμογής εγκαταστάθηκε", + "NotificationOptionAudioPlayback": "Η αναπαραγωγή ήχου ξεκίνησε", + "NotificationOptionAudioPlaybackStopped": "Η αναπαραγωγή ήχου σταμάτησε", + "NotificationOptionCameraImageUploaded": "Μεταφορτώθηκε φωτογραφία απο κάμερα", + "NotificationOptionInstallationFailed": "Αποτυχία εγκατάστασης", + "NotificationOptionNewLibraryContent": "Προστέθηκε νέο περιεχόμενο", + "NotificationOptionPluginError": "Αποτυχία του πρόσθετου", + "NotificationOptionPluginInstalled": "Το πρόσθετο εγκαταστάθηκε", + "NotificationOptionPluginUninstalled": "Το πρόσθετο απεγκαταστάθηκε", + "NotificationOptionPluginUpdateInstalled": "Η ενημέρωση του πρόσθετου εγκαταστάθηκε", + "NotificationOptionServerRestartRequired": "Ο διακομιστής χρειάζεται επανεκκίνηση", + "NotificationOptionTaskFailed": "Αποτυχία προγραμματισμένης εργασίας", + "NotificationOptionUserLockedOut": "Ο χρήστης αποκλείστηκε", + "NotificationOptionVideoPlayback": "Η αναπαραγωγή βίντεο ξεκίνησε", + "NotificationOptionVideoPlaybackStopped": "Η αναπαραγωγή βίντεο σταμάτησε", + "Photos": "Φωτογραφίες", + "Playlists": "Λίστες αναπαραγωγής", + "Plugin": "Πρόσθετο", + "PluginInstalledWithName": "Το {0} εγκαταστάθηκε", + "PluginUninstalledWithName": "Το {0} έχει απεγκατασταθεί", + "PluginUpdatedWithName": "Το {0} ενημερώθηκε", + "ProviderValue": "Πάροχος: {0}", + "ScheduledTaskFailedWithName": "{0} αποτυχία", + "ScheduledTaskStartedWithName": "{0} ξεκίνησε", + "ServerNameNeedsToBeRestarted": "{0} χρειάζεται επανεκκίνηση", + "Shows": "Σειρές", + "Songs": "Τραγούδια", + "StartupEmbyServerIsLoading": "Ο διακομιστής Jellyfin φορτώνει. Περιμένετε λίγο και δοκιμάστε ξανά.", + "SubtitleDownloadFailureFromForItem": "Αποτυχίες μεταφόρτωσης υποτίτλων από {0} για {1}", + "Sync": "Συγχρονισμός", + "System": "Σύστημα", + "TvShows": "Τηλεοπτικές Σειρές", + "User": "Χρήστης", + "UserCreatedWithName": "Ο χρήστης {0} δημιουργήθηκε", + "UserDeletedWithName": "Ο χρήστης {0} έχει διαγραφεί", + "UserDownloadingItemWithValues": "{0} κατεβάζει {1}", + "UserLockedOutWithName": "Ο χρήστης {0} αποκλείστηκε", + "UserOfflineFromDevice": "{0} αποσυνδέθηκε από {1}", + "UserOnlineFromDevice": "{0} είναι online απο {1}", + "UserPasswordChangedWithName": "Ο κωδικός του χρήστη {0} έχει αλλάξει", + "UserPolicyUpdatedWithName": "Η πολιτική χρήστη έχει ενημερωθεί για {0}", + "UserStartedPlayingItemWithValues": "{0} παίζει {1} σε {2}", + "UserStoppedPlayingItemWithValues": "{0} τελείωσε να παίζει {1} σε {2}", + "ValueHasBeenAddedToLibrary": "{0} προστέθηκαν στη βιβλιοθήκη πολυμέσων σας", + "ValueSpecialEpisodeName": "Σπέσιαλ - {0}", + "VersionNumber": "Έκδοση {0}", + "TaskRefreshPeople": "Ανανέωση Ατόμων", + "TaskCleanLogsDescription": "Διαγράφει αρχεία καταγραφής που είναι πάνω από {0} ημέρες.", + "TaskCleanLogs": "Εκκαθάριση Καταλόγου Καταγραφής", + "TaskRefreshLibraryDescription": "Σαρώνει την βιβλιοθήκη πολυμέσων σας για νέα αρχεία και ανανεώνει τα μεταδεδομένα.", + "TaskRefreshLibrary": "Σάρωση Βιβλιοθήκης Πολυμέσων", + "TaskRefreshChapterImagesDescription": "Δημιουργεί μικρογραφίες για βίντεο που έχουν κεφάλαια.", + "TaskRefreshChapterImages": "Εξαγωγή Εικόνων Κεφαλαίου", + "TaskCleanCacheDescription": "Διαγράφει αρχεία προσωρινής μνήμης που δεν χρειάζονται πλέον το σύστημα.", + "TaskCleanCache": "Καθαρισμός Καταλόγου Προσωρινής Μνήμης", + "TasksChannelsCategory": "Κανάλια Διαδικτύου", + "TasksApplicationCategory": "Εφαρμογή", + "TasksLibraryCategory": "Βιβλιοθήκη", + "TasksMaintenanceCategory": "Συντήρηση", + "TaskDownloadMissingSubtitlesDescription": "Ψάχνει στο διαδίκτυο για υπότιτλους που λείπουν με βάση τη διαμόρφωση μεταδεδομένων.", + "TaskDownloadMissingSubtitles": "Λήψη υπότιτλων που λείπουν", + "TaskRefreshChannelsDescription": "Ανανεώνει τις πληροφορίες καναλιού στο διαδικτύου.", + "TaskRefreshChannels": "Ανανέωση Καναλιών", + "TaskCleanTranscodeDescription": "Διαγράφει αρχεία διακωδικοποίησης άνω της μίας ημέρας.", + "TaskCleanTranscode": "Εκκαθάριση Kαταλόγου Διακωδικοποίησης", + "TaskUpdatePluginsDescription": "Κατεβάζει και εγκαθιστά ενημερώσεις για τα πρόσθετα που έχουν ρυθμιστεί για αυτόματη ενημέρωση.", + "TaskUpdatePlugins": "Ενημέρωση Πρόσθετων", + "TaskRefreshPeopleDescription": "Ενημερώνει τα μεταδεδομένα για ηθοποιούς και σκηνοθέτες στη βιβλιοθήκη πολυμέσων σας.", + "TaskCleanActivityLogDescription": "Διαγράφει καταχωρήσεις απο το αρχείο καταγραφής παλαιότερες από την επιλεγμένη ηλικία.", + "TaskCleanActivityLog": "Εκκαθάριση Αρχείου Καταγραφής Δραστηριοτήτων", + "Undefined": "Απροσδιόριστο", + "Forced": "Εξαναγκασμένο", + "Default": "Προεπιλογή", + "TaskOptimizeDatabaseDescription": "Συμπιέζει τη βάση δεδομένων και δημιουργεί ελεύθερο χώρο. Η εκτέλεση αυτής της εργασίας μετά τη σάρωση της βιβλιοθήκης ή την πραγματοποίηση άλλων αλλαγών που συνεπάγονται τροποποιήσεις της βάσης δεδομένων μπορεί να βελτιώσει την απόδοση.", + "TaskOptimizeDatabase": "Βελτιστοποίηση βάσης δεδομένων", + "TaskKeyframeExtractorDescription": "Εξάγει καρέ από αρχεία βίντεο για να δημιουργήσει πιο ακριβείς λίστες αναπαραγωγής HLS. Αυτή η διεργασία μπορεί να πάρει χρόνο.", + "TaskKeyframeExtractor": "Εξαγωγέας βασικών καρέ βίντεο", + "External": "Εξωτερικό", + "HearingImpaired": "Με προβλήματα ακοής", + "TaskRefreshTrickplayImages": "Δημιουργία εικόνων Trickplay", + "TaskRefreshTrickplayImagesDescription": "Δημιουργεί προεπισκοπήσεις trickplay για βίντεο σε ενεργοποιημένες βιβλιοθήκες.", + "TaskAudioNormalization": "Ομοιομορφία ήχου", + "TaskAudioNormalizationDescription": "Ανίχνευση αρχείων για δεδομένα ομοιομορφίας ήχου.", + "TaskCleanCollectionsAndPlaylists": "Καθαρισμός συλλογών και λιστών αναπαραγωγής", + "TaskCleanCollectionsAndPlaylistsDescription": "Αφαιρούνται στοιχεία από τις συλλογές και τις λίστες αναπαραγωγής που δεν υπάρχουν πλέον.", + "TaskMoveTrickplayImages": "Αλλαγή τοποθεσίας εικόνων Trickplay", + "TaskDownloadMissingLyrics": "Λήψη στίχων που λείπουν", + "TaskMoveTrickplayImagesDescription": "Μετακινεί τα υπάρχοντα αρχεία trickplay σύμφωνα με τις ρυθμίσεις της βιβλιοθήκης.", + "TaskDownloadMissingLyricsDescription": "Κατεβάζει στίχους για τραγούδια", + "TaskExtractMediaSegments": "Σάρωση τμημάτων πολυμέσων", + "TaskExtractMediaSegmentsDescription": "Εξάγει ή βρίσκει τμήματα πολυμέσων από επεκτάσεις που χρησιμοποιούν το MediaSegment.", + "CleanupUserDataTaskDescription": "Καθαρίζει όλα τα δεδομένα χρήστη (κατάσταση παρακολούθησης, κατάσταση αγαπημένων κ.λπ.) από πολυμέσα που δεν υπάρχουν πλέον για τουλάχιστον 90 ημέρες.", + "CleanupUserDataTask": "Εργασία εκκαθάρισης δεδομένων χρήστη" +} diff --git a/Emby.Server.Implementations/Localization/Core/en-GB.json b/Emby.Server.Implementations/Localization/Core/en-GB.json new file mode 100644 index 00000000..bd5be0b1 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/en-GB.json @@ -0,0 +1,141 @@ +{ + "Albums": "Albums", + "AppDeviceValues": "App: {0}, Device: {1}", + "Application": "Application", + "Artists": "Artists", + "AuthenticationSucceededWithUserName": "{0} successfully authenticated", + "Books": "Books", + "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", + "Channels": "Channels", + "ChapterNameValue": "Chapter {0}", + "Collections": "Collections", + "DeviceOfflineWithName": "{0} has disconnected", + "DeviceOnlineWithName": "{0} is connected", + "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", + "Favorites": "Favourites", + "Folders": "Folders", + "Genres": "Genres", + "HeaderAlbumArtists": "Album artists", + "HeaderContinueWatching": "Continue Watching", + "HeaderFavoriteAlbums": "Favourite Albums", + "HeaderFavoriteArtists": "Favourite Artists", + "HeaderFavoriteEpisodes": "Favourite Episodes", + "HeaderFavoriteShows": "Favourite Shows", + "HeaderFavoriteSongs": "Favourite Songs", + "HeaderLiveTV": "Live TV", + "HeaderNextUp": "Next Up", + "HeaderRecordingGroups": "Recording Groups", + "HomeVideos": "Home Videos", + "Inherit": "Inherit", + "ItemAddedWithName": "{0} was added to the library", + "ItemRemovedWithName": "{0} was removed from the library", + "LabelIpAddressValue": "IP address: {0}", + "LabelRunningTimeValue": "Running time: {0}", + "Latest": "Latest", + "MessageApplicationUpdated": "Jellyfin Server has been updated", + "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", + "MessageServerConfigurationUpdated": "Server configuration has been updated", + "MixedContent": "Mixed content", + "Movies": "Movies", + "Music": "Music", + "MusicVideos": "Music Videos", + "NameInstallFailed": "{0} installation failed", + "NameSeasonNumber": "Season {0}", + "NameSeasonUnknown": "Season Unknown", + "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", + "NotificationOptionApplicationUpdateAvailable": "Application update available", + "NotificationOptionApplicationUpdateInstalled": "Application update installed", + "NotificationOptionAudioPlayback": "Audio playback started", + "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", + "NotificationOptionCameraImageUploaded": "Camera image uploaded", + "NotificationOptionInstallationFailed": "Installation failure", + "NotificationOptionNewLibraryContent": "New content added", + "NotificationOptionPluginError": "Plugin failure", + "NotificationOptionPluginInstalled": "Plugin installed", + "NotificationOptionPluginUninstalled": "Plugin uninstalled", + "NotificationOptionPluginUpdateInstalled": "Plugin update installed", + "NotificationOptionServerRestartRequired": "Server restart required", + "NotificationOptionTaskFailed": "Scheduled task failure", + "NotificationOptionUserLockedOut": "User locked out", + "NotificationOptionVideoPlayback": "Video playback started", + "NotificationOptionVideoPlaybackStopped": "Video playback stopped", + "Photos": "Photos", + "Playlists": "Playlists", + "Plugin": "Plugin", + "PluginInstalledWithName": "{0} was installed", + "PluginUninstalledWithName": "{0} was uninstalled", + "PluginUpdatedWithName": "{0} was updated", + "ProviderValue": "Provider: {0}", + "ScheduledTaskFailedWithName": "{0} failed", + "ScheduledTaskStartedWithName": "{0} started", + "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "Shows": "Shows", + "Songs": "Songs", + "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", + "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "Sync": "Sync", + "System": "System", + "TvShows": "TV Shows", + "User": "User", + "UserCreatedWithName": "User {0} has been created", + "UserDeletedWithName": "User {0} has been deleted", + "UserDownloadingItemWithValues": "{0} is downloading {1}", + "UserLockedOutWithName": "User {0} has been locked out", + "UserOfflineFromDevice": "{0} has disconnected from {1}", + "UserOnlineFromDevice": "{0} is online from {1}", + "UserPasswordChangedWithName": "Password has been changed for user {0}", + "UserPolicyUpdatedWithName": "User policy has been updated for {0}", + "UserStartedPlayingItemWithValues": "{0} has started playing {1}", + "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", + "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "ValueSpecialEpisodeName": "Special - {0}", + "VersionNumber": "Version {0}", + "TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.", + "TaskDownloadMissingSubtitles": "Download missing subtitles", + "TaskRefreshChannelsDescription": "Refreshes internet channel information.", + "TaskRefreshChannels": "Refresh Channels", + "TaskCleanTranscodeDescription": "Deletes transcode files more than one day old.", + "TaskCleanTranscode": "Clean Transcode Directory", + "TaskUpdatePluginsDescription": "Downloads and installs updates for plugins that are configured to update automatically.", + "TaskUpdatePlugins": "Update Plugins", + "TaskRefreshPeopleDescription": "Updates metadata for actors and directors in your media library.", + "TaskRefreshPeople": "Refresh People", + "TaskCleanLogsDescription": "Deletes log files that are more than {0} days old.", + "TaskCleanLogs": "Clean Log Directory", + "TaskRefreshLibraryDescription": "Scans your media library for new files and refreshes metadata.", + "TaskRefreshLibrary": "Scan Media Library", + "TaskRefreshChapterImagesDescription": "Creates thumbnails for videos that have chapters.", + "TaskRefreshChapterImages": "Extract Chapter Images", + "TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.", + "TaskCleanCache": "Clean Cache Directory", + "TasksChannelsCategory": "Internet Channels", + "TasksApplicationCategory": "Application", + "TasksLibraryCategory": "Library", + "TasksMaintenanceCategory": "Maintenance", + "TaskCleanActivityLogDescription": "Deletes activity log entries older than the configured age.", + "TaskCleanActivityLog": "Clean Activity Log", + "Undefined": "Undefined", + "Forced": "Forced", + "Default": "Default", + "TaskOptimizeDatabaseDescription": "Compacts database and truncates free space. Running this task after scanning the library or doing other changes that imply database modifications might improve performance.", + "TaskOptimizeDatabase": "Optimise database", + "TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time.", + "TaskKeyframeExtractor": "Keyframe Extractor", + "External": "External", + "HearingImpaired": "Hearing Impaired", + "TaskRefreshTrickplayImages": "Generate Trickplay Images", + "TaskRefreshTrickplayImagesDescription": "Creates trickplay previews for videos in enabled libraries.", + "TaskCleanCollectionsAndPlaylists": "Clean up collections and playlists", + "TaskCleanCollectionsAndPlaylistsDescription": "Removes items from collections and playlists that no longer exist.", + "TaskAudioNormalization": "Audio Normalisation", + "TaskAudioNormalizationDescription": "Scans files for audio normalisation data.", + "TaskDownloadMissingLyrics": "Download missing lyrics", + "TaskDownloadMissingLyricsDescription": "Downloads lyrics for songs", + "TaskExtractMediaSegments": "Media Segment Scan", + "TaskExtractMediaSegmentsDescription": "Extracts or obtains media segments from MediaSegment enabled plugins.", + "TaskMoveTrickplayImages": "Migrate Trickplay Image Location", + "TaskMoveTrickplayImagesDescription": "Moves existing trickplay files according to the library settings.", + "CleanupUserDataTask": "User data cleanup task", + "CleanupUserDataTaskDescription": "Cleans all user data (Watch state, favourite status etc) from media that is no longer present for at least 90 days." +} diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json new file mode 100644 index 00000000..c09d5af9 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/en-US.json @@ -0,0 +1,141 @@ +{ + "Albums": "Albums", + "AppDeviceValues": "App: {0}, Device: {1}", + "Application": "Application", + "Artists": "Artists", + "AuthenticationSucceededWithUserName": "{0} successfully authenticated", + "Books": "Books", + "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", + "Channels": "Channels", + "ChapterNameValue": "Chapter {0}", + "Collections": "Collections", + "Default": "Default", + "DeviceOfflineWithName": "{0} has disconnected", + "DeviceOnlineWithName": "{0} is connected", + "External": "External", + "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", + "Favorites": "Favorites", + "Folders": "Folders", + "Forced": "Forced", + "Genres": "Genres", + "HeaderAlbumArtists": "Album artists", + "HeaderContinueWatching": "Continue Watching", + "HeaderFavoriteAlbums": "Favorite Albums", + "HeaderFavoriteArtists": "Favorite Artists", + "HeaderFavoriteEpisodes": "Favorite Episodes", + "HeaderFavoriteShows": "Favorite Shows", + "HeaderFavoriteSongs": "Favorite Songs", + "HeaderLiveTV": "Live TV", + "HeaderNextUp": "Next Up", + "HeaderRecordingGroups": "Recording Groups", + "HearingImpaired": "Hearing Impaired", + "HomeVideos": "Home Videos", + "Inherit": "Inherit", + "ItemAddedWithName": "{0} was added to the library", + "ItemRemovedWithName": "{0} was removed from the library", + "LabelIpAddressValue": "IP address: {0}", + "LabelRunningTimeValue": "Running time: {0}", + "Latest": "Latest", + "MessageApplicationUpdated": "Jellyfin Server has been updated", + "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", + "MessageServerConfigurationUpdated": "Server configuration has been updated", + "MixedContent": "Mixed content", + "Movies": "Movies", + "Music": "Music", + "MusicVideos": "Music Videos", + "NameInstallFailed": "{0} installation failed", + "NameSeasonNumber": "Season {0}", + "NameSeasonUnknown": "Season Unknown", + "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", + "NotificationOptionApplicationUpdateAvailable": "Application update available", + "NotificationOptionApplicationUpdateInstalled": "Application update installed", + "NotificationOptionAudioPlayback": "Audio playback started", + "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", + "NotificationOptionCameraImageUploaded": "Camera image uploaded", + "NotificationOptionInstallationFailed": "Installation failure", + "NotificationOptionNewLibraryContent": "New content added", + "NotificationOptionPluginError": "Plugin failure", + "NotificationOptionPluginInstalled": "Plugin installed", + "NotificationOptionPluginUninstalled": "Plugin uninstalled", + "NotificationOptionPluginUpdateInstalled": "Plugin update installed", + "NotificationOptionServerRestartRequired": "Server restart required", + "NotificationOptionTaskFailed": "Scheduled task failure", + "NotificationOptionUserLockedOut": "User locked out", + "NotificationOptionVideoPlayback": "Video playback started", + "NotificationOptionVideoPlaybackStopped": "Video playback stopped", + "Photos": "Photos", + "Playlists": "Playlists", + "Plugin": "Plugin", + "PluginInstalledWithName": "{0} was installed", + "PluginUninstalledWithName": "{0} was uninstalled", + "PluginUpdatedWithName": "{0} was updated", + "ProviderValue": "Provider: {0}", + "ScheduledTaskFailedWithName": "{0} failed", + "ScheduledTaskStartedWithName": "{0} started", + "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "Shows": "Shows", + "Songs": "Songs", + "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", + "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "Sync": "Sync", + "System": "System", + "TvShows": "TV Shows", + "Undefined": "Undefined", + "User": "User", + "UserCreatedWithName": "User {0} has been created", + "UserDeletedWithName": "User {0} has been deleted", + "UserDownloadingItemWithValues": "{0} is downloading {1}", + "UserLockedOutWithName": "User {0} has been locked out", + "UserOfflineFromDevice": "{0} has disconnected from {1}", + "UserOnlineFromDevice": "{0} is online from {1}", + "UserPasswordChangedWithName": "Password has been changed for user {0}", + "UserPolicyUpdatedWithName": "User policy has been updated for {0}", + "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}", + "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", + "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "ValueSpecialEpisodeName": "Special - {0}", + "VersionNumber": "Version {0}", + "TasksMaintenanceCategory": "Maintenance", + "TasksLibraryCategory": "Library", + "TasksApplicationCategory": "Application", + "TasksChannelsCategory": "Internet Channels", + "TaskCleanActivityLog": "Clean Activity Log", + "TaskCleanActivityLogDescription": "Deletes activity log entries older than the configured age.", + "TaskCleanCache": "Clean Cache Directory", + "TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.", + "TaskRefreshChapterImages": "Extract Chapter Images", + "TaskRefreshChapterImagesDescription": "Creates thumbnails for videos that have chapters.", + "TaskAudioNormalization": "Audio Normalization", + "TaskAudioNormalizationDescription": "Scans files for audio normalization data.", + "TaskRefreshLibrary": "Scan Media Library", + "TaskRefreshLibraryDescription": "Scans your media library for new files and refreshes metadata.", + "TaskCleanLogs": "Clean Log Directory", + "TaskCleanLogsDescription": "Deletes log files that are more than {0} days old.", + "TaskRefreshPeople": "Refresh People", + "TaskRefreshPeopleDescription": "Updates metadata for actors and directors in your media library.", + "TaskRefreshTrickplayImages": "Generate Trickplay Images", + "TaskRefreshTrickplayImagesDescription": "Creates trickplay previews for videos in enabled libraries.", + "TaskUpdatePlugins": "Update Plugins", + "TaskUpdatePluginsDescription": "Downloads and installs updates for plugins that are configured to update automatically.", + "TaskCleanTranscode": "Clean Transcode Directory", + "TaskCleanTranscodeDescription": "Deletes transcode files more than one day old.", + "TaskRefreshChannels": "Refresh Channels", + "TaskRefreshChannelsDescription": "Refreshes internet channel information.", + "TaskDownloadMissingLyrics": "Download missing lyrics", + "TaskDownloadMissingLyricsDescription": "Downloads lyrics for songs", + "TaskDownloadMissingSubtitles": "Download missing subtitles", + "TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.", + "TaskOptimizeDatabase": "Optimize database", + "TaskOptimizeDatabaseDescription": "Compacts database and truncates free space. Running this task after scanning the library or doing other changes that imply database modifications might improve performance.", + "TaskKeyframeExtractor": "Keyframe Extractor", + "TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time.", + "TaskCleanCollectionsAndPlaylists": "Clean up collections and playlists", + "TaskCleanCollectionsAndPlaylistsDescription": "Removes items from collections and playlists that no longer exist.", + "TaskExtractMediaSegments": "Media Segment Scan", + "TaskExtractMediaSegmentsDescription": "Extracts or obtains media segments from MediaSegment enabled plugins.", + "TaskMoveTrickplayImages": "Migrate Trickplay Image Location", + "TaskMoveTrickplayImagesDescription": "Moves existing trickplay files according to the library settings.", + "CleanupUserDataTask": "User data cleanup task", + "CleanupUserDataTaskDescription": "Cleans all user data (Watch state, favorite status etc) from media that is no longer present for at least 90 days." +} diff --git a/Emby.Server.Implementations/Localization/Core/enm.json b/Emby.Server.Implementations/Localization/Core/enm.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/enm.json @@ -0,0 +1 @@ +{} diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json new file mode 100644 index 00000000..42cce109 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/eo.json @@ -0,0 +1,130 @@ +{ + "NotificationOptionInstallationFailed": "Instalada malsukceso", + "NotificationOptionAudioPlaybackStopped": "Ludado de sono haltis", + "NotificationOptionAudioPlayback": "Ludado de sono lanĉis", + "NameSeasonUnknown": "Sezono Nekonata", + "NameSeasonNumber": "Sezono {0}", + "NameInstallFailed": "{0} instalado fiaskis", + "Music": "Muziko", + "Movies": "Filmoj", + "ItemRemovedWithName": "{0} forigis el la plurmediteko", + "ItemAddedWithName": "{0} aldonis al la plurmediteko", + "HeaderLiveTV": "TV-etero", + "HeaderContinueWatching": "Daŭrigi Spektadon", + "HeaderAlbumArtists": "Artistoj de albumo", + "Folders": "Dosierujoj", + "DeviceOnlineWithName": "{0} estas konektita", + "Default": "Defaŭlte", + "Collections": "Kolektoj", + "ChapterNameValue": "Ĉapitro {0}", + "Channels": "Kanaloj", + "Books": "Libroj", + "Artists": "Artistoj", + "Application": "Aplikaĵo", + "AppDeviceValues": "Aplikaĵo: {0}, Aparato: {1}", + "Albums": "Albumoj", + "TasksLibraryCategory": "Plurmediteko", + "VersionNumber": "Versio {0}", + "UserDownloadingItemWithValues": "{0} elŝutas {1}", + "UserCreatedWithName": "Uzanto {0} kreiĝis", + "User": "Uzanto", + "System": "Sistemo", + "Songs": "Kantoj", + "ScheduledTaskStartedWithName": "{0} lanĉis", + "ScheduledTaskFailedWithName": "{0} malsukcesis", + "PluginUninstalledWithName": "{0} malinstaliĝis", + "PluginInstalledWithName": "{0} instaliĝis", + "Plugin": "Kromprogramo", + "Playlists": "Ludlistoj", + "Photos": "Fotoj", + "NotificationOptionPluginUninstalled": "Kromprogramo malinstaliĝis", + "NotificationOptionNewLibraryContent": "Nova enhavo aldoniĝis", + "NotificationOptionPluginInstalled": "Kromprogramo instaliĝis", + "MusicVideos": "Muzikvideoj", + "LabelIpAddressValue": "IP-adreso: {0}", + "Genres": "Ĝenroj", + "DeviceOfflineWithName": "{0} malkonektis", + "HeaderFavoriteArtists": "Favorataj Artistoj", + "Shows": "Serioj", + "HeaderFavoriteShows": "Favorataj Serioj", + "TvShows": "TV-serioj", + "Favorites": "Favorataj", + "TaskCleanLogs": "Purigi Ĵurnalan Katalogon", + "TaskRefreshLibrary": "Skani Plurmeditekon", + "ValueSpecialEpisodeName": "Speciala - {0}", + "TaskOptimizeDatabase": "Optimumigi datenbazon", + "TaskRefreshChannels": "Refreŝigi Kanalojn", + "TaskUpdatePlugins": "Ĝisdatigi Kromprogramojn", + "TaskRefreshPeople": "Refreŝigi Homojn", + "TasksChannelsCategory": "Interretaj Kanaloj", + "ProviderValue": "Provizanto: {0}", + "NotificationOptionPluginError": "Kromprogramo malsukcesis", + "MixedContent": "Miksita enhavo", + "TasksApplicationCategory": "Aplikaĵo", + "TasksMaintenanceCategory": "Prizorgado", + "Undefined": "Nedifinita", + "Sync": "Sinkronigo", + "Latest": "Plej novaj", + "Inherit": "Hereda", + "HomeVideos": "Hejmaj Videoj", + "HeaderNextUp": "Sekva Plue", + "HeaderFavoriteSongs": "Favorataj Kantoj", + "HeaderFavoriteEpisodes": "Favorataj Epizodoj", + "HeaderFavoriteAlbums": "Favorataj Albumoj", + "Forced": "Forcita", + "ServerNameNeedsToBeRestarted": "{0} devas esti relanĉita", + "NotificationOptionVideoPlayback": "La videoludado lanĉis", + "NotificationOptionServerRestartRequired": "Servila relanĉigo bezonata", + "TaskOptimizeDatabaseDescription": "Kompaktigas datenbazon kaj trunkas liberan lokon. Lanĉi ĉi tiun taskon post la plurmediteka skanado aŭ fari aliajn ŝanĝojn, kiuj implicas datenbazajn modifojn, povus plibonigi rendimenton.", + "TaskUpdatePluginsDescription": "Elŝutas kaj instalas ĝisdatigojn por kromprogramojn, kiuj estas agorditaj por ĝisdatigi aŭtomate.", + "TaskDownloadMissingSubtitlesDescription": "Serĉas en interreto mankantajn subtekstojn surbaze de metadatena agordaro.", + "TaskRefreshPeopleDescription": "Ĝisdatigas metadatenojn por aktoroj kaj reĵisoroj en via plurmediteko.", + "TaskCleanLogsDescription": "Forigas ĵurnalajn dosierojn aĝajn pli ol {0} tagojn.", + "TaskRefreshLibraryDescription": "Skanas vian plurmeditekon por novaj dosieroj kaj refreŝigas metadatenaron.", + "NewVersionIsAvailable": "Nova versio de Jellyfin Server estas elŝutebla.", + "TaskCleanCacheDescription": "Forigas stapla dosierojn ne plu necesajn de la sistemo.", + "TaskCleanActivityLogDescription": "Forigas aktivecan ĵurnalaĵojn pli malnovajn ol la agordita aĝo.", + "TaskCleanTranscodeDescription": "Forigas transkodajn dosierojn aĝajn pli ol unu tagon.", + "ValueHasBeenAddedToLibrary": "{0} estis aldonita al via plurmediteko", + "SubtitleDownloadFailureFromForItem": "Subtekstoj malsukcesis elŝuti de {0} por {1}", + "StartupEmbyServerIsLoading": "Jellyfin Server ŝarĝas. Provi denove baldaŭ.", + "TaskRefreshChapterImagesDescription": "Kreas bildetojn por videoj kiuj havas ĉapitrojn.", + "UserStoppedPlayingItemWithValues": "{0} finis ludi {1} ĉe {2}", + "UserPolicyUpdatedWithName": "Uzanta politiko estis ĝisdatigita por {0}", + "UserPasswordChangedWithName": "Pasvorto estis ŝanĝita por uzanto {0}", + "UserStartedPlayingItemWithValues": "{0} ludas {1} ĉe {2}", + "UserLockedOutWithName": "Uzanto {0} estas elŝlosita", + "UserOnlineFromDevice": "{0} estas enreta de {1}", + "UserOfflineFromDevice": "{0} malkonektis de {1}", + "UserDeletedWithName": "Uzanto {0} estis forigita", + "MessageServerConfigurationUpdated": "Servila agordaro estis ĝisdatigita", + "MessageNamedServerConfigurationUpdatedWithValue": "Servila agorda sekcio {0} estis ĝisdatigita", + "MessageApplicationUpdatedTo": "Jellyfin Server estis ĝisdatigita al {0}", + "MessageApplicationUpdated": "Jellyfin Server estis ĝisdatigita", + "TaskRefreshChannelsDescription": "Refreŝigas informon pri interretaj kanaloj.", + "TaskDownloadMissingSubtitles": "Elŝuti mankantajn subtekstojn", + "TaskCleanTranscode": "Malplenigi Transkodadan Katalogon", + "TaskRefreshChapterImages": "Eltiri Ĉapitrajn Bildojn", + "TaskCleanCache": "Malplenigi Staplan Katalogon", + "TaskCleanActivityLog": "Malplenigi Aktivecan Ĵurnalon", + "PluginUpdatedWithName": "{0} estis ĝisdatigita", + "NotificationOptionVideoPlaybackStopped": "La videoludado haltis", + "NotificationOptionUserLockedOut": "Uzanto ŝlosita", + "NotificationOptionTaskFailed": "Planita tasko malsukcesis", + "NotificationOptionPluginUpdateInstalled": "Ĝisdatigo de kromprogramo instalita", + "NotificationOptionCameraImageUploaded": "Kamera bildo alŝutita", + "NotificationOptionApplicationUpdateInstalled": "Aplikaĵa ĝisdatigo instalita", + "NotificationOptionApplicationUpdateAvailable": "Ĝisdatigo de aplikaĵo havebla", + "LabelRunningTimeValue": "Ludada tempo: {0}", + "HeaderRecordingGroups": "Rikordadaj Grupoj", + "FailedLoginAttemptWithUserName": "Malsukcesa ensaluta provo de {0}", + "CameraImageUploadedFrom": "Nova kamera bildo estis alŝutita de {0}", + "AuthenticationSucceededWithUserName": "{0} sukcese aŭtentikigis", + "TaskKeyframeExtractorDescription": "Eltiras ĉefkadrojn el videodosieroj por krei pli precizajn HLS-ludlistojn. Ĉi tiu tasko povas funkcii dum longa tempo.", + "TaskKeyframeExtractor": "Eltiri Ĉefkadrojn", + "External": "Ekstera", + "TaskAudioNormalizationDescription": "Skanas dosierojn por sonnivelaj normaligaj datumoj.", + "TaskRefreshTrickplayImages": "Generi la bildojn por TrickPlay (Antaŭrigardo rapida antaŭen)", + "TaskAudioNormalization": "Normaligo Sonnivela", + "HearingImpaired": "Surda" +} diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json new file mode 100644 index 00000000..2bbf0d51 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/es-AR.json @@ -0,0 +1,141 @@ +{ + "Albums": "Álbumes", + "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}", + "Application": "Aplicación", + "Artists": "Artistas", + "AuthenticationSucceededWithUserName": "{0} autenticado correctamente", + "Books": "Libros", + "CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}", + "Channels": "Canales", + "ChapterNameValue": "Capítulo {0}", + "Collections": "Colecciones", + "DeviceOfflineWithName": "{0} se ha desconectado", + "DeviceOnlineWithName": "{0} está conectado", + "FailedLoginAttemptWithUserName": "Error al intentar iniciar sesión de {0}", + "Favorites": "Favoritos", + "Folders": "Carpetas", + "Genres": "Géneros", + "HeaderAlbumArtists": "Artistas del álbum", + "HeaderContinueWatching": "Seguir viendo", + "HeaderFavoriteAlbums": "Álbumes favoritos", + "HeaderFavoriteArtists": "Artistas favoritos", + "HeaderFavoriteEpisodes": "Capítulos favoritos", + "HeaderFavoriteShows": "Series favoritas", + "HeaderFavoriteSongs": "Canciones favoritas", + "HeaderLiveTV": "TV en vivo", + "HeaderNextUp": "Siguiente", + "HeaderRecordingGroups": "Grupos de grabación", + "HomeVideos": "Videos caseros", + "Inherit": "Heredar", + "ItemAddedWithName": "{0} se ha añadido a la biblioteca", + "ItemRemovedWithName": "{0} ha sido eliminado de la biblioteca", + "LabelIpAddressValue": "Dirección IP: {0}", + "LabelRunningTimeValue": "Tiempo de funcionamiento: {0}", + "Latest": "Últimos", + "MessageApplicationUpdated": "El servidor Jellyfin fue actualizado", + "MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Se ha actualizado la sección {0} de la configuración del servidor", + "MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor", + "MixedContent": "Contenido mezclado", + "Movies": "Películas", + "Music": "Música", + "MusicVideos": "Videos musicales", + "NameInstallFailed": "{0} instalación fallida", + "NameSeasonNumber": "Temporada {0}", + "NameSeasonUnknown": "Temporada desconocida", + "NewVersionIsAvailable": "Una nueva versión del servidor Jellyfin está disponible para descargar.", + "NotificationOptionApplicationUpdateAvailable": "Actualización de la aplicación disponible", + "NotificationOptionApplicationUpdateInstalled": "Actualización de la aplicación instalada", + "NotificationOptionAudioPlayback": "Se inició la reproducción de audio", + "NotificationOptionAudioPlaybackStopped": "Se detuvo la reproducción de audio", + "NotificationOptionCameraImageUploaded": "Imagen de la cámara subida", + "NotificationOptionInstallationFailed": "Error de instalación", + "NotificationOptionNewLibraryContent": "Nuevo contenido añadido", + "NotificationOptionPluginError": "Falla de complemento", + "NotificationOptionPluginInstalled": "Complemento instalado", + "NotificationOptionPluginUninstalled": "Complemento desinstalado", + "NotificationOptionPluginUpdateInstalled": "Actualización de complemento instalada", + "NotificationOptionServerRestartRequired": "Se necesita reiniciar el servidor", + "NotificationOptionTaskFailed": "Falla de tarea programada", + "NotificationOptionUserLockedOut": "Usuario bloqueado", + "NotificationOptionVideoPlayback": "Se inició la reproducción de video", + "NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida", + "Photos": "Fotos", + "Playlists": "Listas de reproducción", + "Plugin": "Complemento", + "PluginInstalledWithName": "{0} fue instalado", + "PluginUninstalledWithName": "{0} fue desinstalado", + "PluginUpdatedWithName": "{0} fue actualizado", + "ProviderValue": "Proveedor: {0}", + "ScheduledTaskFailedWithName": "{0} falló", + "ScheduledTaskStartedWithName": "{0} iniciado", + "ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado", + "Shows": "Series", + "Songs": "Canciones", + "StartupEmbyServerIsLoading": "El servidor Jellyfin se está cargando. Vuelve a intentarlo en breve.", + "SubtitleDownloadFailureFromForItem": "Falló la descarga de subtitulos desde {0} para {1}", + "Sync": "Sincronizar", + "System": "Sistema", + "TvShows": "Series de TV", + "User": "Usuario", + "UserCreatedWithName": "El usuario {0} ha sido creado", + "UserDeletedWithName": "El usuario {0} ha sido borrado", + "UserDownloadingItemWithValues": "{0} está descargando {1}", + "UserLockedOutWithName": "El usuario {0} ha sido bloqueado", + "UserOfflineFromDevice": "{0} se ha desconectado de {1}", + "UserOnlineFromDevice": "{0} está en línea desde {1}", + "UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}", + "UserPolicyUpdatedWithName": "Las política de usuario ha sido actualizada para {0}", + "UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}", + "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}", + "ValueHasBeenAddedToLibrary": "{0} ha sido añadido a tu biblioteca multimedia", + "ValueSpecialEpisodeName": "Especial - {0}", + "VersionNumber": "Versión {0}", + "TaskDownloadMissingSubtitlesDescription": "Busca en internet los subtítulos que falten basándose en la configuración de los metadatos.", + "TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes", + "TaskRefreshChannelsDescription": "Actualizar información de canales de internet.", + "TaskRefreshChannels": "Actualizar canales", + "TaskCleanTranscodeDescription": "Eliminar archivos transcodificados con mas de un día de antigüedad.", + "TaskCleanTranscode": "Limpiar directorio de transcodificación", + "TaskUpdatePluginsDescription": "Descargar e instalar actualizaciones para complementos que estén configurados en actualizar automáticamente.", + "TaskUpdatePlugins": "Actualizar complementos", + "TaskRefreshPeopleDescription": "Actualizar metadatos de actores y directores en su biblioteca multimedia.", + "TaskRefreshPeople": "Actualizar personas", + "TaskCleanLogsDescription": "Eliminar archivos de registro que tengan mas de {0} días de antigüedad.", + "TaskCleanLogs": "Limpiar directorio de registros", + "TaskRefreshLibraryDescription": "Escanear su biblioteca multimedia por nuevos archivos y refrescar metadatos.", + "TaskRefreshLibrary": "Escanear biblioteca multimedia", + "TaskRefreshChapterImagesDescription": "Crear miniaturas de videos que tengan capítulos.", + "TaskRefreshChapterImages": "Extraer imágenes de capítulo", + "TaskCleanCacheDescription": "Eliminar archivos de caché que no se necesiten en el sistema.", + "TaskCleanCache": "Limpiar directorio caché", + "TasksChannelsCategory": "Canales de internet", + "TasksApplicationCategory": "Aplicación", + "TasksLibraryCategory": "Biblioteca", + "TasksMaintenanceCategory": "Mantenimiento", + "TaskCleanActivityLogDescription": "Borrar log de actividades anteriores a la fecha establecida.", + "TaskCleanActivityLog": "Borrar log de actividades", + "Undefined": "Indefinido", + "Forced": "Forzado", + "Default": "Predeterminado", + "TaskOptimizeDatabaseDescription": "Compacta la base de datos y restaura el espacio libre. Ejecutar esta tarea después de actualizar las librerías o realizar otros cambios que impliquen modificar las bases de datos puede mejorar la performance.", + "TaskOptimizeDatabase": "Optimización de base de datos", + "External": "Externo", + "TaskKeyframeExtractorDescription": "Extrae Fotogramas Clave de los archivos de vídeo para crear Listas de Reprodución HLS más precisas. Esta tarea puede durar mucho tiempo.", + "TaskKeyframeExtractor": "Extractor de Fotogramas Clave", + "HearingImpaired": "Discapacidad Auditiva", + "TaskRefreshTrickplayImages": "Generar imágenes de Trickplay", + "TaskRefreshTrickplayImagesDescription": "Crea vistas previas de reproducción engañosa para videos en bibliotecas habilitadas.", + "TaskAudioNormalization": "Normalización de audio", + "TaskAudioNormalizationDescription": "Escanea archivos en busca de datos de normalización de audio.", + "TaskCleanCollectionsAndPlaylists": "Limpiar colecciones y listas de reproducción", + "TaskCleanCollectionsAndPlaylistsDescription": "Elimina elementos de colecciones y listas de reproducción que ya no existen.", + "TaskDownloadMissingLyrics": "Descargar letra faltante", + "TaskDownloadMissingLyricsDescription": "Descarga letras de canciones", + "TaskExtractMediaSegments": "Escanear Segmentos de Media", + "TaskExtractMediaSegmentsDescription": "Extrae u obtiene segmentos de medio de plugins habilitados para MediaSegment.", + "TaskMoveTrickplayImagesDescription": "Mueve archivos existentes de trickplay de acuerdo a la configuración de la biblioteca.", + "TaskMoveTrickplayImages": "Migrar Ubicación de Imagen de Trickplay", + "CleanupUserDataTaskDescription": "Limpia todos los datos del usuario (estado de visualización, estado de los favoritos, etc.) que no están presentes en la biblioteca por al menos 90 días.", + "CleanupUserDataTask": "Tarea de limpieza de datos de usuarios" +} diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json new file mode 100644 index 00000000..6748fff4 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/es-MX.json @@ -0,0 +1,141 @@ +{ + "Albums": "Álbumes", + "AppDeviceValues": "App: {0}, Dispositivo: {1}", + "Application": "Aplicación", + "Artists": "Artistas", + "AuthenticationSucceededWithUserName": "{0} autenticado con éxito", + "Books": "Libros", + "CameraImageUploadedFrom": "Una nueva imagen de cámara ha sido subida desde {0}", + "Channels": "Canales", + "ChapterNameValue": "Capítulo {0}", + "Collections": "Colecciones", + "DeviceOfflineWithName": "{0} se ha desconectado", + "DeviceOnlineWithName": "{0} está conectado", + "FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión de {0}", + "Favorites": "Favoritos", + "Folders": "Carpetas", + "Genres": "Géneros", + "HeaderAlbumArtists": "Artistas del Álbum", + "HeaderContinueWatching": "Continuar viendo", + "HeaderFavoriteAlbums": "Álbumes favoritos", + "HeaderFavoriteArtists": "Artistas favoritos", + "HeaderFavoriteEpisodes": "Episodios favoritos", + "HeaderFavoriteShows": "Programas favoritos", + "HeaderFavoriteSongs": "Canciones favoritas", + "HeaderLiveTV": "TV en vivo", + "HeaderNextUp": "A continuación", + "HeaderRecordingGroups": "Grupos de grabación", + "HomeVideos": "Videos Caseros", + "Inherit": "Heredar", + "ItemAddedWithName": "{0} fue agregado a la biblioteca", + "ItemRemovedWithName": "{0} fue removido de la biblioteca", + "LabelIpAddressValue": "Dirección IP: {0}", + "LabelRunningTimeValue": "Tiempo corriendo: {0}", + "Latest": "Recientes", + "MessageApplicationUpdated": "El servidor Jellyfin ha sido actualizado", + "MessageApplicationUpdatedTo": "El servidor Jellyfin ha sido actualizado a {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Se ha actualizado la sección {0} de la configuración del servidor", + "MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor", + "MixedContent": "Contenido mezclado", + "Movies": "Películas", + "Music": "Música", + "MusicVideos": "Videos Musicales", + "NameInstallFailed": "Falló la instalación de {0}", + "NameSeasonNumber": "Temporada {0}", + "NameSeasonUnknown": "Temporada desconocida", + "NewVersionIsAvailable": "Una nueva versión del Servidor Jellyfin está disponible para descargar.", + "NotificationOptionApplicationUpdateAvailable": "Actualización de la aplicación disponible", + "NotificationOptionApplicationUpdateInstalled": "Actualización de la aplicación instalada", + "NotificationOptionAudioPlayback": "Reproducción de audio iniciada", + "NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida", + "NotificationOptionCameraImageUploaded": "Imagen de la cámara subida", + "NotificationOptionInstallationFailed": "Fallo en la instalación", + "NotificationOptionNewLibraryContent": "Nuevo contenido agregado", + "NotificationOptionPluginError": "Falla de complemento", + "NotificationOptionPluginInstalled": "Complemento instalado", + "NotificationOptionPluginUninstalled": "Complemento desinstalado", + "NotificationOptionPluginUpdateInstalled": "Actualización de complemento instalada", + "NotificationOptionServerRestartRequired": "Se necesita reiniciar el servidor", + "NotificationOptionTaskFailed": "Falla de tarea programada", + "NotificationOptionUserLockedOut": "Usuario bloqueado", + "NotificationOptionVideoPlayback": "Reproducción de video iniciada", + "NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida", + "Photos": "Fotos", + "Playlists": "Listas de reproducción", + "Plugin": "Complemento", + "PluginInstalledWithName": "{0} fue instalado", + "PluginUninstalledWithName": "{0} fue desinstalado", + "PluginUpdatedWithName": "{0} fue actualizado", + "ProviderValue": "Proveedor: {0}", + "ScheduledTaskFailedWithName": "{0} falló", + "ScheduledTaskStartedWithName": "{0} iniciado", + "ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado", + "Shows": "Programas", + "Songs": "Canciones", + "StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.", + "SubtitleDownloadFailureFromForItem": "Falló la descarga de subtítulos desde {0} para {1}", + "Sync": "Sincronizar", + "System": "Sistema", + "TvShows": "Programas de TV", + "User": "Usuario", + "UserCreatedWithName": "El usuario {0} ha sido creado", + "UserDeletedWithName": "El usuario {0} ha sido eliminado", + "UserDownloadingItemWithValues": "{0} está descargando {1}", + "UserLockedOutWithName": "El usuario {0} ha sido bloqueado", + "UserOfflineFromDevice": "{0} se ha desconectado desde {1}", + "UserOnlineFromDevice": "{0} está en línea desde {1}", + "UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}", + "UserPolicyUpdatedWithName": "La política de usuario ha sido actualizada para {0}", + "UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}", + "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}", + "ValueHasBeenAddedToLibrary": "{0} se ha añadido a tu biblioteca de medios", + "ValueSpecialEpisodeName": "Especial - {0}", + "VersionNumber": "Versión {0}", + "TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.", + "TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes", + "TaskRefreshChannelsDescription": "Actualiza la información de los canales de Internet.", + "TaskRefreshChannels": "Actualizar canales", + "TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día de antigüedad.", + "TaskCleanTranscode": "Limpiar directorio de transcodificado", + "TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.", + "TaskUpdatePlugins": "Actualizar complementos", + "TaskRefreshPeopleDescription": "Actualiza metadatos de actores y directores en tu biblioteca de medios.", + "TaskRefreshPeople": "Actualizar personas", + "TaskCleanLogsDescription": "Elimina archivos de registro con más de {0} días de antigüedad.", + "TaskCleanLogs": "Limpiar directorio de registros", + "TaskRefreshLibraryDescription": "Escanea tu biblioteca de medios por archivos nuevos y actualiza los metadatos.", + "TaskRefreshLibrary": "Escanear biblioteca de medios", + "TaskRefreshChapterImagesDescription": "Crea miniaturas para videos que tienen capítulos.", + "TaskRefreshChapterImages": "Extraer imágenes de los capítulos", + "TaskCleanCacheDescription": "Elimina archivos caché que ya no son necesarios para el sistema.", + "TaskCleanCache": "Limpiar directorio caché", + "TasksChannelsCategory": "Canales de Internet", + "TasksApplicationCategory": "Aplicación", + "TasksLibraryCategory": "Biblioteca", + "TasksMaintenanceCategory": "Mantenimiento", + "TaskCleanActivityLogDescription": "Elimina entradas del registro de actividad que sean más antiguas al periodo establecido.", + "TaskCleanActivityLog": "Limpiar registro de actividades", + "Undefined": "Sin definir", + "Forced": "Forzado", + "Default": "Predeterminado", + "TaskOptimizeDatabase": "Optimizar base de datos", + "TaskOptimizeDatabaseDescription": "Compacta la base de datos y trunca el espacio libre. Puede mejorar el rendimiento si se realiza esta tarea después de escanear la biblioteca o después de realizar otros cambios que impliquen modificar la base de datos.", + "TaskKeyframeExtractorDescription": "Extrae los cuadros clave de los archivos de vídeo para crear listas HLS más precisas. Esta tarea puede tardar un buen rato.", + "TaskKeyframeExtractor": "Extractor de Cuadros Clave", + "External": "Externo", + "HearingImpaired": "Discapacidad Auditiva", + "TaskRefreshTrickplayImagesDescription": "Crea previsualizaciones para la barra de reproducción en las bibliotecas habilitadas.", + "TaskRefreshTrickplayImages": "Generar imágenes de la barra de reproducción", + "TaskAudioNormalization": "Normalización de audio", + "TaskAudioNormalizationDescription": "Analiza los archivos para normalizar el audio.", + "TaskCleanCollectionsAndPlaylists": "Limpieza de colecciones y listas de reproducción", + "TaskCleanCollectionsAndPlaylistsDescription": "Quita elementos que ya no existen de colecciones y listas de reproducción.", + "TaskDownloadMissingLyrics": "descargar letras que faltan", + "TaskDownloadMissingLyricsDescription": "Descargar letras de canciones", + "TaskExtractMediaSegments": "Escaneo de segmentos de medios", + "TaskExtractMediaSegmentsDescription": "Extrae u obtiene segmentos de medios de plugins habilitados para MediaSegment.", + "TaskMoveTrickplayImages": "Migrar la ubicación de la imagen de Trickplay", + "TaskMoveTrickplayImagesDescription": "Mueve archivos de trickplay existentes según la configuración de la biblioteca.", + "CleanupUserDataTask": "Tarea de limpieza de los datos del usuario", + "CleanupUserDataTaskDescription": "Limpia toda la información de usuario (Estado de última vez visto, favoritos, etc) del archivo media que no está presente por los últimos 90 días." +} diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json new file mode 100644 index 00000000..b9c57afe --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -0,0 +1,141 @@ +{ + "Albums": "Álbumes", + "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}", + "Application": "Aplicación", + "Artists": "Artistas", + "AuthenticationSucceededWithUserName": "{0} autenticado correctamente", + "Books": "Libros", + "CameraImageUploadedFrom": "Se ha subido una nueva imagen por cámara desde {0}", + "Channels": "Canales", + "ChapterNameValue": "Capítulo {0}", + "Collections": "Colecciones", + "DeviceOfflineWithName": "{0} se ha desconectado", + "DeviceOnlineWithName": "{0} está conectado", + "FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión de {0}", + "Favorites": "Favoritos", + "Folders": "Carpetas", + "Genres": "Géneros", + "HeaderAlbumArtists": "Artistas del álbum", + "HeaderContinueWatching": "Seguir viendo", + "HeaderFavoriteAlbums": "Álbumes favoritos", + "HeaderFavoriteArtists": "Artistas favoritos", + "HeaderFavoriteEpisodes": "Episodios favoritos", + "HeaderFavoriteShows": "Series favoritas", + "HeaderFavoriteSongs": "Canciones favoritas", + "HeaderLiveTV": "Televisión en directo", + "HeaderNextUp": "Siguiente", + "HeaderRecordingGroups": "Grupos de grabación", + "HomeVideos": "Vídeos caseros", + "Inherit": "Heredar", + "ItemAddedWithName": "{0} se ha añadido a la biblioteca", + "ItemRemovedWithName": "{0} ha sido eliminado de la biblioteca", + "LabelIpAddressValue": "Dirección IP: {0}", + "LabelRunningTimeValue": "Duración: {0}", + "Latest": "Últimas", + "MessageApplicationUpdated": "Se ha actualizado el servidor Jellyfin", + "MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "La sección {0} de configuración del servidor ha sido actualizada", + "MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor", + "MixedContent": "Contenido mixto", + "Movies": "Películas", + "Music": "Música", + "MusicVideos": "Vídeos musicales", + "NameInstallFailed": "{0} error de instalación", + "NameSeasonNumber": "Temporada {0}", + "NameSeasonUnknown": "Temporada desconocida", + "NewVersionIsAvailable": "Disponible una nueva versión de Jellyfin para descargar.", + "NotificationOptionApplicationUpdateAvailable": "Actualización de la aplicación disponible", + "NotificationOptionApplicationUpdateInstalled": "Actualización de la aplicación instalada", + "NotificationOptionAudioPlayback": "Se inició la reproducción de audio", + "NotificationOptionAudioPlaybackStopped": "Se detuvo la reproducción de audio", + "NotificationOptionCameraImageUploaded": "Imagen de la cámara cargada", + "NotificationOptionInstallationFailed": "Error en la instalación", + "NotificationOptionNewLibraryContent": "Nuevo contenido añadido", + "NotificationOptionPluginError": "Error en plugin", + "NotificationOptionPluginInstalled": "Plugin instalado", + "NotificationOptionPluginUninstalled": "Plugin desinstalado", + "NotificationOptionPluginUpdateInstalled": "Actualización del complemento instalada", + "NotificationOptionServerRestartRequired": "Se requiere reinicio del servidor", + "NotificationOptionTaskFailed": "Error de tarea programada", + "NotificationOptionUserLockedOut": "Usuario bloqueado", + "NotificationOptionVideoPlayback": "Se inició la reproducción de vídeo", + "NotificationOptionVideoPlaybackStopped": "Reproducción de vídeo detenida", + "Photos": "Fotos", + "Playlists": "Listas de reproducción", + "Plugin": "Plugin", + "PluginInstalledWithName": "{0} se ha instalado", + "PluginUninstalledWithName": "{0} se ha desinstalado", + "PluginUpdatedWithName": "{0} se actualizó", + "ProviderValue": "Proveedor: {0}", + "ScheduledTaskFailedWithName": "{0} falló", + "ScheduledTaskStartedWithName": "{0} iniciada", + "ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado", + "Shows": "Series", + "Songs": "Canciones", + "StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.", + "SubtitleDownloadFailureFromForItem": "Fallo en la descarga de subtítulos desde {0} para {1}", + "Sync": "Sincronizar", + "System": "Sistema", + "TvShows": "Series", + "User": "Usuario", + "UserCreatedWithName": "El usuario {0} ha sido creado", + "UserDeletedWithName": "El usuario {0} ha sido borrado", + "UserDownloadingItemWithValues": "{0} está descargando {1}", + "UserLockedOutWithName": "El usuario {0} ha sido bloqueado", + "UserOfflineFromDevice": "{0} se ha desconectado desde {1}", + "UserOnlineFromDevice": "{0} está en línea desde {1}", + "UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}", + "UserPolicyUpdatedWithName": "Actualizada política de usuario para {0}", + "UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}", + "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}", + "ValueHasBeenAddedToLibrary": "{0} ha sido añadido a tu biblioteca multimedia", + "ValueSpecialEpisodeName": "Especial - {0}", + "VersionNumber": "Versión {0}", + "TasksMaintenanceCategory": "Mantenimiento", + "TasksLibraryCategory": "Biblioteca", + "TasksApplicationCategory": "Aplicación", + "TasksChannelsCategory": "Canales de internet", + "TaskCleanCache": "Eliminar archivos temporales", + "TaskCleanCacheDescription": "Elimina los archivos temporales que ya no son necesarios para el servidor.", + "TaskRefreshChapterImages": "Extraer imágenes de los capítulos", + "TaskRefreshChapterImagesDescription": "Crear miniaturas de los vídeos que tengan capítulos.", + "TaskRefreshLibrary": "Escanear la biblioteca", + "TaskRefreshLibraryDescription": "Añade los archivos que se hayan añadido a la biblioteca y actualiza las etiquetas de los ya presentes.", + "TaskCleanLogs": "Limpiar registros", + "TaskCleanLogsDescription": "Elimina los archivos de registro que tengan más de {0} días.", + "TaskRefreshPeople": "Actualizar personas", + "TaskRefreshPeopleDescription": "Actualiza las etiquetas de los actores y directores presentes en tus bibliotecas.", + "TaskUpdatePlugins": "Actualizar extensiones", + "TaskUpdatePluginsDescription": "Actualiza las extensiones que están configuradas para actualizarse automáticamente.", + "TaskCleanTranscode": "Limpiar las transcodificaciones", + "TaskCleanTranscodeDescription": "Elimina los archivos temporales de transcodificación anteriores a un día de antigüedad.", + "TaskRefreshChannels": "Actualizar canales", + "TaskRefreshChannelsDescription": "Actualiza la información de los canales de internet.", + "TaskDownloadMissingSubtitles": "Descargar los subtítulos que faltan", + "TaskDownloadMissingSubtitlesDescription": "Busca en internet los subtítulos que falten en el contenido de tus bibliotecas, basándose en la configuración de los metadatos.", + "TaskCleanActivityLogDescription": "Elimina todos los registros de actividad anteriores a la fecha configurada.", + "TaskCleanActivityLog": "Limpiar registro de actividad", + "Undefined": "Indefinido", + "Forced": "Forzado", + "Default": "Predeterminado", + "TaskOptimizeDatabase": "Optimizar la base de datos", + "TaskOptimizeDatabaseDescription": "Optimiza y libera el espacio libre en la base de datos. Ejecutar esta tarea tras escanear la biblioteca o hacer cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento.", + "TaskKeyframeExtractorDescription": "Extrae los fotogramas clave de los archivos de vídeo para crear listas HLS más precisas. Esta tarea puede tardar mucho tiempo.", + "TaskKeyframeExtractor": "Extractor de Fotogramas Clave", + "External": "Externo", + "HearingImpaired": "Discapacidad Auditiva", + "TaskRefreshTrickplayImages": "Generar miniaturas de línea de tiempo", + "TaskRefreshTrickplayImagesDescription": "Crear miniaturas de tiempo para videos en las librerías habilitadas.", + "TaskCleanCollectionsAndPlaylists": "Limpiar colecciones y listas de reproducción", + "TaskCleanCollectionsAndPlaylistsDescription": "Elimina elementos de colecciones y listas de reproducción que ya no existen.", + "TaskAudioNormalization": "Normalización de audio", + "TaskAudioNormalizationDescription": "Escanear archivos para obtener datos de normalización.", + "TaskDownloadMissingLyricsDescription": "Descargar letras para las canciones", + "TaskDownloadMissingLyrics": "Descargar letras faltantes", + "TaskMoveTrickplayImagesDescription": "Mueve archivos de trickplay existentes según la configuración de la biblioteca.", + "TaskExtractMediaSegments": "Escaneo de segmentos de medios", + "TaskExtractMediaSegmentsDescription": "Extrae u obtiene segmentos de medios de plugins habilitados para MediaSegment.", + "TaskMoveTrickplayImages": "Migrar la ubicación de la imagen de Trickplay", + "CleanupUserDataTask": "Tarea de limpieza de datos del usuario", + "CleanupUserDataTaskDescription": "Limpia todos los datos del usuario (estado de visualización, favoritos, etc.) de los medios que ya no están disponibles desde hace al menos 90 días." +} diff --git a/Emby.Server.Implementations/Localization/Core/es_419.json b/Emby.Server.Implementations/Localization/Core/es_419.json new file mode 100644 index 00000000..34c68c33 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/es_419.json @@ -0,0 +1,141 @@ +{ + "LabelRunningTimeValue": "Tiempo en ejecución: {0}", + "ValueSpecialEpisodeName": "Especial - {0}", + "Sync": "Sincronizar", + "Songs": "Canciones", + "Shows": "Programas", + "Playlists": "Listas de reproducción", + "Photos": "Fotos", + "Movies": "Películas", + "HeaderNextUp": "A continuación", + "HeaderLiveTV": "TV en vivo", + "HeaderFavoriteSongs": "Canciones favoritas", + "HeaderFavoriteArtists": "Artistas favoritos", + "HeaderFavoriteAlbums": "Álbumes favoritos", + "HeaderFavoriteEpisodes": "Episodios favoritos", + "HeaderFavoriteShows": "Programas favoritos", + "HeaderContinueWatching": "Continuar viendo", + "HeaderAlbumArtists": "Artistas de álbum", + "Genres": "Géneros", + "Folders": "Carpetas", + "Favorites": "Favoritos", + "Collections": "Colecciones", + "Channels": "Canales", + "Books": "Libros", + "Artists": "Artistas", + "Albums": "Álbumes", + "TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.", + "TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes", + "TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.", + "TaskRefreshChannels": "Actualizar canales", + "TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día.", + "TaskCleanTranscode": "Limpiar el directorio de transcodificaciones", + "TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.", + "TaskUpdatePlugins": "Actualizar complementos", + "TaskRefreshPeopleDescription": "Actualiza metadatos de actores y directores en tu biblioteca de medios.", + "TaskRefreshPeople": "Actualizar personas", + "TaskCleanLogsDescription": "Elimina archivos de registro con más de {0} días de antigüedad.", + "TaskCleanLogs": "Limpiar directorio de registros", + "TaskRefreshLibraryDescription": "Escanea tu biblioteca de medios por archivos nuevos y actualiza los metadatos.", + "TaskRefreshLibrary": "Escanear biblioteca de medios", + "TaskRefreshChapterImagesDescription": "Crea miniaturas para videos que tienen capítulos.", + "TaskRefreshChapterImages": "Extraer imágenes de los capítulos", + "TaskCleanCacheDescription": "Elimina archivos caché que ya no son necesarios para el sistema.", + "TaskCleanCache": "Limpiar directorio caché", + "TasksChannelsCategory": "Canales de Internet", + "TasksApplicationCategory": "Aplicación", + "TasksLibraryCategory": "Biblioteca", + "TasksMaintenanceCategory": "Mantenimiento", + "VersionNumber": "Versión {0}", + "ValueHasBeenAddedToLibrary": "{0} se ha añadido a tu biblioteca de medios", + "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}", + "UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}", + "UserPolicyUpdatedWithName": "La política de usuario ha sido actualizada para {0}", + "UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}", + "UserOnlineFromDevice": "{0} está en línea desde {1}", + "UserOfflineFromDevice": "{0} se ha desconectado desde {1}", + "UserLockedOutWithName": "El usuario {0} ha sido bloqueado", + "UserDownloadingItemWithValues": "{0} está descargando {1}", + "UserDeletedWithName": "El usuario {0} ha sido eliminado", + "UserCreatedWithName": "El usuario {0} ha sido creado", + "User": "Usuario", + "TvShows": "Programas de TV", + "System": "Sistema", + "SubtitleDownloadFailureFromForItem": "Falló la descarga de subtítulos desde {0} para {1}", + "StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.", + "ServerNameNeedsToBeRestarted": "{0} debe ser reiniciado", + "ScheduledTaskStartedWithName": "{0} iniciado", + "ScheduledTaskFailedWithName": "{0} falló", + "ProviderValue": "Proveedor: {0}", + "PluginUpdatedWithName": "{0} fue actualizado", + "PluginUninstalledWithName": "{0} fue desinstalado", + "PluginInstalledWithName": "{0} fue instalado", + "Plugin": "Complemento", + "NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida", + "NotificationOptionVideoPlayback": "Reproducción de video iniciada", + "NotificationOptionUserLockedOut": "Usuario bloqueado", + "NotificationOptionTaskFailed": "Falla de tarea programada", + "NotificationOptionServerRestartRequired": "Se necesita reiniciar el servidor", + "NotificationOptionPluginUpdateInstalled": "Actualización de complemento instalada", + "NotificationOptionPluginUninstalled": "Complemento desinstalado", + "NotificationOptionPluginInstalled": "Complemento instalado", + "NotificationOptionPluginError": "Falla de complemento", + "NotificationOptionNewLibraryContent": "Nuevo contenido agregado", + "NotificationOptionInstallationFailed": "Falla de instalación", + "NotificationOptionCameraImageUploaded": "Imagen de la cámara subida", + "NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida", + "NotificationOptionAudioPlayback": "Reproducción de audio iniciada", + "NotificationOptionApplicationUpdateInstalled": "Actualización de la aplicación instalada", + "NotificationOptionApplicationUpdateAvailable": "Actualización de la aplicación disponible", + "NewVersionIsAvailable": "Una nueva versión del Servidor Jellyfin está disponible para descargar.", + "NameSeasonUnknown": "Temporada desconocida", + "NameSeasonNumber": "Temporada {0}", + "NameInstallFailed": "Falló la instalación de {0}", + "MusicVideos": "Videos musicales", + "Music": "Música", + "MixedContent": "Contenido mezclado", + "MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor", + "MessageNamedServerConfigurationUpdatedWithValue": "Se ha actualizado la sección {0} de la configuración del servidor", + "MessageApplicationUpdatedTo": "El servidor Jellyfin ha sido actualizado a {0}", + "MessageApplicationUpdated": "El servidor Jellyfin ha sido actualizado", + "Latest": "Recientes", + "LabelIpAddressValue": "Dirección IP: {0}", + "ItemRemovedWithName": "{0} fue removido de la biblioteca", + "ItemAddedWithName": "{0} fue agregado a la biblioteca", + "Inherit": "Heredar", + "HomeVideos": "Videos caseros", + "HeaderRecordingGroups": "Grupos de grabación", + "FailedLoginAttemptWithUserName": "Intento de inicio de sesión fallido desde {0}", + "DeviceOnlineWithName": "{0} está conectado", + "DeviceOfflineWithName": "{0} se ha desconectado", + "ChapterNameValue": "Capítulo {0}", + "CameraImageUploadedFrom": "Una nueva imagen de cámara ha sido subida desde {0}", + "AuthenticationSucceededWithUserName": "{0} autenticado con éxito", + "Application": "Aplicación", + "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}", + "TaskCleanActivityLogDescription": "Elimina las entradas del registro de actividad anteriores al periodo configurado.", + "TaskCleanActivityLog": "Limpiar registro de actividades", + "Undefined": "Sin definir", + "Forced": "Forzado", + "Default": "Por defecto", + "TaskOptimizeDatabaseDescription": "Compacta la base de datos y libera espacio. Ejecutar esta tarea después de escanear la biblioteca o hacer otros cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento.", + "TaskOptimizeDatabase": "Optimizar base de datos", + "External": "Externo", + "TaskKeyframeExtractorDescription": "Extrae Fotogramas Clave de los archivos de vídeo para crear Listas de Reproducción HLS más precisas. Esta tarea puede durar mucho tiempo.", + "TaskKeyframeExtractor": "Extractor de Fotogramas Clave", + "HearingImpaired": "Discapacidad auditiva", + "TaskRefreshTrickplayImagesDescription": "Crea previsualizaciones para la barra de reproducción en las bibliotecas habilitadas.", + "TaskRefreshTrickplayImages": "Generar imágenes de la barra de reproducción", + "TaskAudioNormalization": "Normalización de audio", + "TaskCleanCollectionsAndPlaylistsDescription": "Quita elementos que ya no existen de colecciones y listas de reproducción.", + "TaskAudioNormalizationDescription": "Analiza los archivos para normalizar el audio.", + "TaskCleanCollectionsAndPlaylists": "Limpieza de colecciones y listas de reproducción", + "TaskDownloadMissingLyrics": "Descargar letra faltante", + "TaskDownloadMissingLyricsDescription": "Descarga letras de canciones", + "TaskExtractMediaSegmentsDescription": "Extrae u obtiene segmentos de medios de complementos habilitados para MediaSegment.", + "TaskMoveTrickplayImagesDescription": "Mueve archivos de trickplay existentes según la configuración de la biblioteca.", + "TaskExtractMediaSegments": "Escaneo de segmentos de medios", + "TaskMoveTrickplayImages": "Migrar la ubicación de la imagen de Trickplay", + "CleanupUserDataTask": "Tarea de limpieza de datos de usuario", + "CleanupUserDataTaskDescription": "Limpia todos los datos de usuario (estado de visualización, favoritos, etc.) que no están presentes en la biblioteca por al menos 90 días." +} diff --git a/Emby.Server.Implementations/Localization/Core/es_DO.json b/Emby.Server.Implementations/Localization/Core/es_DO.json new file mode 100644 index 00000000..f98a5e5b --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/es_DO.json @@ -0,0 +1,135 @@ +{ + "Channels": "Canales", + "Books": "Libros", + "Albums": "Álbumes", + "Collections": "Colecciones", + "Artists": "Artistas", + "DeviceOnlineWithName": "{0} está conectado", + "DeviceOfflineWithName": "{0} se ha desconectado", + "ChapterNameValue": "Capítulo {0}", + "CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}", + "AuthenticationSucceededWithUserName": "{0} autenticado con éxito", + "Application": "Aplicación", + "AppDeviceValues": "App: {0}, Dispositivo: {1}", + "HeaderContinueWatching": "Continuar Viendo", + "HeaderAlbumArtists": "Artistas del álbum", + "Genres": "Géneros", + "Folders": "Carpetas", + "Favorites": "Favoritos", + "FailedLoginAttemptWithUserName": "Intento de inicio de sesión fallido desde {0}", + "HeaderFavoriteSongs": "Canciones Favoritas", + "HeaderFavoriteEpisodes": "Episodios Favoritos", + "HeaderFavoriteArtists": "Artistas Favoritos", + "External": "Externo", + "Default": "Predeterminado", + "Movies": "Películas", + "MessageNamedServerConfigurationUpdatedWithValue": "La sección {0} de la configuración ha sido actualizada", + "MixedContent": "Contenido mixto", + "Music": "Música", + "NotificationOptionCameraImageUploaded": "Imagen de la cámara subida", + "NotificationOptionServerRestartRequired": "Se necesita reiniciar el servidor", + "NotificationOptionVideoPlayback": "Reproducción de video iniciada", + "Sync": "Sincronizar", + "Shows": "Series", + "UserDownloadingItemWithValues": "{0} está descargando {1}", + "UserOfflineFromDevice": "{0} se ha desconectado desde {1}", + "UserOnlineFromDevice": "{0} está en línea desde {1}", + "TasksChannelsCategory": "Canales de Internet", + "TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.", + "TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes", + "TaskOptimizeDatabaseDescription": "Compacta la base de datos y libera espacio. Ejecutar esta tarea después de escanear la biblioteca o hacer otros cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento.", + "TaskKeyframeExtractorDescription": "Extrae Fotogramas Clave de los archivos de vídeo para crear Listas de Reproducción HLS más precisas. Esta tarea puede durar mucho tiempo.", + "TaskAudioNormalization": "Normalización de audio", + "TaskAudioNormalizationDescription": "Escanear archivos para la normalización de data.", + "TaskCleanCollectionsAndPlaylists": "Limpiar colecciones y listas de reproducción", + "TaskCleanCollectionsAndPlaylistsDescription": "Remover elementos de colecciones y listas de reproducción que no existen.", + "TvShows": "Series de TV", + "UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}", + "TaskRefreshChannels": "Actualizar canales", + "Photos": "Fotos", + "HeaderFavoriteShows": "Programas favoritos", + "TaskCleanActivityLog": "Limpiar registro de actividades", + "UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}", + "System": "Sistema", + "User": "Usuario", + "Forced": "Forzado", + "PluginInstalledWithName": "{0} ha sido instalado", + "HeaderFavoriteAlbums": "Álbumes favoritos", + "TaskUpdatePlugins": "Actualizar Plugins", + "Latest": "Recientes", + "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}", + "Songs": "Canciones", + "NotificationOptionPluginError": "Falla de plugin", + "ScheduledTaskStartedWithName": "{0} iniciado", + "TasksApplicationCategory": "Aplicación", + "UserDeletedWithName": "El usuario {0} ha sido eliminado", + "TaskRefreshChapterImages": "Extraer imágenes de los capítulos", + "TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para plugins que están configurados para actualizarse automáticamente.", + "TaskRefreshPeopleDescription": "Actualiza metadatos de actores y directores en tu biblioteca de medios.", + "NotificationOptionUserLockedOut": "Usuario bloqueado", + "TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día.", + "TaskCleanTranscode": "Limpiar el directorio de transcodificaciones", + "NotificationOptionPluginUpdateInstalled": "Actualización de plugin instalada", + "NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida", + "TasksLibraryCategory": "Biblioteca", + "NotificationOptionPluginInstalled": "Plugin instalado", + "UserPolicyUpdatedWithName": "La política de usuario ha sido actualizada para {0}", + "VersionNumber": "Versión {0}", + "HeaderNextUp": "A continuación", + "ValueHasBeenAddedToLibrary": "{0} se ha añadido a tu biblioteca", + "LabelIpAddressValue": "Dirección IP: {0}", + "NameSeasonNumber": "Temporada {0}", + "NotificationOptionNewLibraryContent": "Nuevo contenido agregado", + "Plugin": "Plugin", + "NotificationOptionAudioPlayback": "Reproducción de audio iniciada", + "NotificationOptionTaskFailed": "Falló la tarea programada", + "LabelRunningTimeValue": "Tiempo en ejecución: {0}", + "SubtitleDownloadFailureFromForItem": "Falló la descarga de subtítulos desde {0} para {1}", + "TaskRefreshLibrary": "Escanear biblioteca de medios", + "ServerNameNeedsToBeRestarted": "{0} debe ser reiniciado", + "TasksMaintenanceCategory": "Mantenimiento", + "ProviderValue": "Proveedor: {0}", + "UserCreatedWithName": "El usuario {0} ha sido creado", + "PluginUninstalledWithName": "{0} ha sido desinstalado", + "ValueSpecialEpisodeName": "Especial - {0}", + "ScheduledTaskFailedWithName": "{0} falló", + "TaskCleanLogs": "Limpiar directorio de registros", + "NameInstallFailed": "Falló la instalación de {0}", + "UserLockedOutWithName": "El usuario {0} ha sido bloqueado", + "TaskRefreshLibraryDescription": "Escanea tu biblioteca de medios para encontrar archivos nuevos y actualizar los metadatos.", + "StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo en un momento.", + "Playlists": "Listas de reproducción", + "TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.", + "MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor", + "TaskRefreshPeople": "Actualizar personas", + "NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida", + "HeaderLiveTV": "TV en vivo", + "NameSeasonUnknown": "Temporada desconocida", + "NotificationOptionInstallationFailed": "Fallo de instalación", + "NotificationOptionPluginUninstalled": "Plugin desinstalado", + "TaskCleanCache": "Limpiar directorio caché", + "TaskRefreshChapterImagesDescription": "Crea miniaturas para videos que tienen capítulos.", + "Inherit": "Heredar", + "HeaderRecordingGroups": "Grupos de grabación", + "ItemAddedWithName": "{0} fue agregado a la biblioteca", + "TaskOptimizeDatabase": "Optimizar base de datos", + "TaskKeyframeExtractor": "Extractor de Fotogramas Clave", + "HearingImpaired": "Discapacidad auditiva", + "HomeVideos": "Videos caseros", + "ItemRemovedWithName": "{0} fue removido de la biblioteca", + "MessageApplicationUpdated": "El servidor Jellyfin ha sido actualizado", + "MessageApplicationUpdatedTo": "El servidor Jellyfin ha sido actualizado a {0}", + "MusicVideos": "Videos musicales", + "NewVersionIsAvailable": "Una nueva versión de Jellyfin está disponible para descargar.", + "PluginUpdatedWithName": "{0} ha sido actualizado", + "Undefined": "Sin definir", + "TaskCleanActivityLogDescription": "Elimina las entradas del registro de actividad anteriores al periodo configurado.", + "TaskCleanCacheDescription": "Elimina archivos caché que ya no son necesarios para el sistema.", + "TaskCleanLogsDescription": "Elimina archivos de registro con más de {0} días de antigüedad.", + "NotificationOptionApplicationUpdateAvailable": "actualización disponible", + "TaskDownloadMissingLyrics": "Descargue letras desaparecidas", + "TaskDownloadMissingLyricsDescription": "Decarga letras para canciones", + "TaskMoveTrickplayImages": "Mover localización de foto vista previa", + "NotificationOptionApplicationUpdateInstalled": "Aplicación actualización disponible", + "CleanupUserDataTask": "Tarea de limpieza de los datos del usuario" +} diff --git a/Emby.Server.Implementations/Localization/Core/et.json b/Emby.Server.Implementations/Localization/Core/et.json new file mode 100644 index 00000000..91a0aa66 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/et.json @@ -0,0 +1,141 @@ +{ + "TaskCleanActivityLogDescription": "Kustutab määratud ajast vanemad tegevuslogi kirjed.", + "UserDownloadingItemWithValues": "{0} laadib alla {1}", + "HeaderRecordingGroups": "Salvestusrühmad", + "TaskOptimizeDatabaseDescription": "Tihendab ja puhastab andmebaasi. Selle toimingu tegemine pärast meediakogu andmebaasiga seotud muudatuste skannimist võib jõudlust parandada.", + "TaskOptimizeDatabase": "Optimeeri andmebaasi", + "TaskDownloadMissingSubtitlesDescription": "Otsib veebist puuduvaid subtiitreid vastavalt määratud metaandmete seadetele.", + "TaskDownloadMissingSubtitles": "Hangi puuduvad subtiitrid", + "TaskRefreshChannelsDescription": "Värskendab veebikanalite teavet.", + "TaskRefreshChannels": "Värskenda kanaleid", + "TaskCleanTranscodeDescription": "Kustutab üle ühe päeva vanused transkoodimisfailid.", + "TaskCleanTranscode": "Puhasta transkoodimise kataloog", + "TaskUpdatePluginsDescription": "Laadib alla ja paigaldab nende pluginate uuendused, mis on seadistatud automaatselt uuenduma.", + "TaskUpdatePlugins": "Uuenda pluginaid", + "TaskRefreshPeopleDescription": "Värskendab meediakogus näitlejate ja režissööride metaandmeid.", + "TaskRefreshPeople": "Värskenda inimesi", + "TaskCleanLogsDescription": "Kustutab logifailid, mis on vanemad kui {0} päeva.", + "TaskCleanLogs": "Puhasta logikataloog", + "TaskRefreshLibraryDescription": "Otsib meedikogust uusi faile ja värskendab metaandmeid.", + "Collections": "Kogumikud", + "TaskRefreshLibrary": "Skaneeri meediakogu", + "TaskRefreshChapterImagesDescription": "Loob peatükkidega videote jaoks pisipildid.", + "TaskRefreshChapterImages": "Eralda peatükipildid", + "TaskCleanCacheDescription": "Kustutab vahemälufailid, mida süsteem enam ei vaja.", + "TaskCleanCache": "Puhasta vahemälu kataloog", + "TaskCleanActivityLog": "Puhasta tegevuslogi", + "TasksChannelsCategory": "Veebikanalid", + "TasksApplicationCategory": "Rakendus", + "TasksLibraryCategory": "Meediakogu", + "TasksMaintenanceCategory": "Hooldus", + "VersionNumber": "Versioon {0}", + "ValueSpecialEpisodeName": "Eriepisood - {0}", + "ValueHasBeenAddedToLibrary": "{0} lisati meediakogusse", + "UserStartedPlayingItemWithValues": "{0} taasesitab {1} seadmes {2}", + "UserPasswordChangedWithName": "Kasutaja {0} parool muudeti", + "UserLockedOutWithName": "Kasutaja {0} lukustati", + "UserDeletedWithName": "Kasutaja {0} kustutati", + "UserCreatedWithName": "Kasutaja {0} on loodud", + "ScheduledTaskStartedWithName": "{0} käivitati", + "ProviderValue": "Allikas: {0}", + "StartupEmbyServerIsLoading": "Jellyfin server laadib. Proovi varsti uuesti.", + "User": "Kasutaja", + "Undefined": "Määratlemata", + "TvShows": "Sarjad", + "System": "Süsteem", + "Sync": "Sünkrooni", + "Songs": "Lood", + "Shows": "Sarjad", + "ServerNameNeedsToBeRestarted": "{0} tuleb taaskäivitada", + "ScheduledTaskFailedWithName": "{0} nurjus", + "PluginUpdatedWithName": "{0} uuendati", + "PluginUninstalledWithName": "{0} eemaldati", + "PluginInstalledWithName": "{0} paigaldati", + "Plugin": "Plugin", + "Playlists": "Esitusloendid", + "Photos": "Fotod", + "NotificationOptionVideoPlaybackStopped": "Video taasesitus lõppes", + "NotificationOptionVideoPlayback": "Video taasesitus algas", + "NotificationOptionUserLockedOut": "Kasutaja lukustati", + "NotificationOptionTaskFailed": "Ajastatud ülesanne nurjus", + "NotificationOptionServerRestartRequired": "Vajalik on serveri taaskäivitamine", + "NotificationOptionPluginUpdateInstalled": "Paigaldati plugina uuendus", + "NotificationOptionPluginUninstalled": "Plugin eemaldati", + "NotificationOptionPluginInstalled": "Plugin paigaldati", + "NotificationOptionPluginError": "Plugina tõrge", + "NotificationOptionNewLibraryContent": "Lisati uut sisu", + "NotificationOptionInstallationFailed": "Paigaldamine nurjus", + "NotificationOptionCameraImageUploaded": "Kaamera pilt on üles laaditud", + "NotificationOptionAudioPlaybackStopped": "Heli taasesitus lõppes", + "NotificationOptionAudioPlayback": "Heli taasesitus algas", + "NotificationOptionApplicationUpdateInstalled": "Rakenduse uuendus paigaldati", + "NotificationOptionApplicationUpdateAvailable": "Rakenduse uuendus on saadaval", + "NewVersionIsAvailable": "Jellyfin serveri uus versioon on allalaadimiseks saadaval.", + "NameSeasonUnknown": "Tundmatu hooaeg", + "NameSeasonNumber": "{0}. hooaeg", + "NameInstallFailed": "{0} paigaldamine nurjus", + "MusicVideos": "Muusikavideod", + "Music": "Muusika", + "Movies": "Filmid", + "MixedContent": "Segatud sisu", + "MessageServerConfigurationUpdated": "Serveri seadistust uuendati", + "MessageNamedServerConfigurationUpdatedWithValue": "Serveri seadistusosa {0} uuendati", + "MessageApplicationUpdatedTo": "Jellyfin server uuendati versioonile {0}", + "MessageApplicationUpdated": "Jellyfin server uuendati", + "Latest": "Uusimad", + "LabelRunningTimeValue": "Kestus: {0}", + "LabelIpAddressValue": "IP aadress: {0}", + "ItemRemovedWithName": "{0} eemaldati meediakogust", + "ItemAddedWithName": "{0} lisati meediakogusse", + "Inherit": "Päri", + "HomeVideos": "Koduvideod", + "HeaderNextUp": "Järgmisena", + "HeaderLiveTV": "Otse TV", + "HeaderFavoriteSongs": "Lemmiklood", + "HeaderFavoriteShows": "Lemmiksarjad", + "HeaderFavoriteEpisodes": "Lemmikepisoodid", + "HeaderFavoriteArtists": "Lemmikesitajad", + "HeaderFavoriteAlbums": "Lemmikalbumid", + "HeaderContinueWatching": "Jätka vaatamist", + "HeaderAlbumArtists": "Albumi esitajad", + "Genres": "Žanrid", + "Forced": "Sunnitud", + "Folders": "Kaustad", + "Favorites": "Lemmikud", + "FailedLoginAttemptWithUserName": "Sisselogimine nurjus aadressilt {0}", + "DeviceOnlineWithName": "{0} on ühendatud", + "DeviceOfflineWithName": "{0} katkestas ühenduse", + "Default": "Vaikimisi", + "ChapterNameValue": "Peatükk {0}", + "Channels": "Kanalid", + "CameraImageUploadedFrom": "Uus kaamera pilt laaditi üles allikalt {0}", + "Books": "Raamatud", + "AuthenticationSucceededWithUserName": "{0} autentimine õnnestus", + "Artists": "Esitajad", + "Application": "Rakendus", + "AppDeviceValues": "Rakendus: {0}, seade: {1}", + "Albums": "Albumid", + "UserOfflineFromDevice": "{0} katkestas ühenduse seadmega {1}", + "SubtitleDownloadFailureFromForItem": "Subtiitrite allalaadimine {0} > {1} nurjus", + "UserPolicyUpdatedWithName": "Kasutaja {0} õigusi värskendati", + "UserStoppedPlayingItemWithValues": "{0} lõpetas {1} taasesituse seadmes {2}", + "UserOnlineFromDevice": "{0} on ühendatud seadmest {1}", + "External": "Väline", + "HearingImpaired": "Kuulmispuudega", + "TaskKeyframeExtractorDescription": "Eraldab videofailidest võtmekaadrid, et luua täpsemaid HLS-i esitusloendeid. See võib kesta pikka aega.", + "TaskKeyframeExtractor": "Eralda võtmekaadrid", + "TaskRefreshTrickplayImages": "Loo trickplay pildid", + "TaskRefreshTrickplayImagesDescription": "Loob trickplay eelvaated videotele lubatud meediakogudes.", + "TaskAudioNormalization": "Normaliseeri helitugevus", + "TaskAudioNormalizationDescription": "Otsib failidest helitugevuse normaliseerimise teavet.", + "TaskCleanCollectionsAndPlaylistsDescription": "Eemaldab kogumikest ja esitusloenditest üksused, mida enam ei eksisteeri.", + "TaskCleanCollectionsAndPlaylists": "Puhasta kogumikud ja esitusloendid", + "TaskDownloadMissingLyrics": "Hangi puuduvad laulusõnad", + "TaskDownloadMissingLyricsDescription": "Laulusõnade allalaadimine", + "TaskMoveTrickplayImagesDescription": "Liigutab trickplay pildid meediakogu sätete kohaselt.", + "TaskExtractMediaSegments": "Skaneeri meediasegmente", + "TaskExtractMediaSegmentsDescription": "Eraldab või võtab meediasegmendid MediaSegment'i lubavatest pluginatest.", + "TaskMoveTrickplayImages": "Muuda trickplay piltide asukoht", + "CleanupUserDataTask": "Puhasta kasutajaandmed", + "CleanupUserDataTaskDescription": "Puhastab kõik kasutajaandmed (vaatamise olek, lemmikute olek jne) meediast, mida pole enam vähemalt 90 päeva saadaval olnud." +} diff --git a/Emby.Server.Implementations/Localization/Core/eu.json b/Emby.Server.Implementations/Localization/Core/eu.json new file mode 100644 index 00000000..c9a798ca --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/eu.json @@ -0,0 +1,141 @@ +{ + "ValueSpecialEpisodeName": "Berezia - {0}", + "Sync": "Sinkronizatu", + "Songs": "Abestiak", + "Shows": "Serieak", + "Playlists": "Erreprodukzio-zerrendak", + "Photos": "Argazkiak", + "MusicVideos": "Bideo musikalak", + "Movies": "Filmak", + "HeaderContinueWatching": "Ikusten jarraitu", + "HeaderAlbumArtists": "Albumeko artistak", + "Genres": "Generoak", + "Folders": "Karpetak", + "Favorites": "Gogokoak", + "Default": "Lehenetsia", + "Collections": "Bildumak", + "Channels": "Kanalak", + "Books": "Liburuak", + "Artists": "Artistak", + "Albums": "Albumak", + "TaskOptimizeDatabase": "Datu basea optimizatu", + "TaskDownloadMissingSubtitlesDescription": "Falta diren azpitituluak bilatzen ditu interneten metadatuen konfigurazioaren arabera.", + "TaskDownloadMissingSubtitles": "Falta diren azpitituluak deskargatu", + "TaskRefreshChannelsDescription": "Internet kanalen informazioa eguneratu.", + "TaskRefreshChannels": "Kanalak eguneratu", + "TaskCleanTranscodeDescription": "Egun bat baino zaharragoak diren transkodifikazio fitxategiak ezabatzen ditu.", + "TaskCleanTranscode": "Transkodifikazio direktorioa garbitu", + "TaskUpdatePluginsDescription": "Automatikoki deskargatu eta instalatu eguneraketak konfiguratutako pluginetarako.", + "TaskUpdatePlugins": "Pluginak eguneratu", + "TaskRefreshPeopleDescription": "Zure liburutegiko aktore eta zuzendarien metadatuak eguneratzen ditu.", + "TaskRefreshPeople": "Jendea eguneratu", + "TaskCleanLogsDescription": "{0} egun baino zaharragoak diren log fitxategiak ezabatzen ditu.", + "TaskCleanLogs": "Log direktorioa garbitu", + "TaskRefreshLibraryDescription": "Zure multimedia liburutegia eskaneatzen du fitxategi berriak eta metadatuak eguneratzeko.", + "TaskRefreshLibrary": "Multimedia liburutegia eskaneatu", + "TaskRefreshChapterImagesDescription": "Kapituluak dituzten bideoen miniaturak sortzen ditu.", + "TaskRefreshChapterImages": "Kapituluen irudiak erauzi", + "TaskCleanCacheDescription": "Sistemak behar ez dituen cache fitxategiak ezabatzen ditu.", + "TaskCleanCache": "Cache direktorioa garbitu", + "TaskCleanActivityLogDescription": "Konfiguratutako baino zaharragoak diren jarduera-log sarrerak ezabatzen ditu.", + "TaskCleanActivityLog": "Erabilera Log-a garbitu", + "TasksChannelsCategory": "Internet Kanalak", + "TasksApplicationCategory": "Aplikazioa", + "TasksLibraryCategory": "Liburutegia", + "TasksMaintenanceCategory": "Mantenua", + "VersionNumber": "Bertsioa {0}", + "ValueHasBeenAddedToLibrary": "{0} zure multimedia liburutegian gehitu da", + "UserStoppedPlayingItemWithValues": "{0} {1} ikusten bukatu du {2}-(e)n", + "UserStartedPlayingItemWithValues": "{0} {1} ikusten ari da {2}-(e)n", + "UserPolicyUpdatedWithName": "{0} erabiltzailearen politikak aldatu dira", + "UserPasswordChangedWithName": "{0} erabiltzailearen pasahitza aldatu da", + "UserOnlineFromDevice": "{0} online dago {1}-(e)tik", + "UserOfflineFromDevice": "{0} {1}-(e)tik deskonektatu da", + "UserLockedOutWithName": "{0} erabiltzailea blokeatu da", + "UserDownloadingItemWithValues": "{0} {1} deskargatzen ari da", + "UserDeletedWithName": "{0} Erabiltzailea ezabatu da", + "UserCreatedWithName": "{0} Erabiltzailea sortu da", + "User": "Erabiltzailea", + "Undefined": "Ezezaguna", + "TvShows": "TB serieak", + "System": "Sistema", + "SubtitleDownloadFailureFromForItem": "{1}-en azpitutuluak {0}-tik deskargatzeak huts egin du", + "StartupEmbyServerIsLoading": "Jellyfin zerbitzaria kargatzen. Saiatu berriro beranduago.", + "ServerNameNeedsToBeRestarted": "{0} berrabiarazi behar da", + "ScheduledTaskStartedWithName": "{0} hasi da", + "ScheduledTaskFailedWithName": "{0} huts egin du", + "PluginUpdatedWithName": "{0} eguneratu da", + "PluginUninstalledWithName": "{0} desinstalatu da", + "PluginInstalledWithName": "{0} instalatu da", + "Plugin": "Plugin", + "NotificationOptionVideoPlaybackStopped": "Bideoa geldituta", + "NotificationOptionVideoPlayback": "Bideoa martxan", + "NotificationOptionUserLockedOut": "Erabiltzailea blokeatua", + "NotificationOptionTaskFailed": "Programatutako atazak huts egin du", + "NotificationOptionServerRestartRequired": "Zerbitzaria berrabiarazi behar da", + "NotificationOptionPluginUpdateInstalled": "Pluginaren eguneraketa instalatua", + "NotificationOptionPluginUninstalled": "Plugina desinstalatua", + "NotificationOptionPluginInstalled": "Plugina instalatua", + "NotificationOptionPluginError": "Pluginak huts egin du", + "NotificationOptionNewLibraryContent": "Eduki berria gehitua", + "NotificationOptionInstallationFailed": "Instalazioak huts egin du", + "NotificationOptionCameraImageUploaded": "Kamerako irudia igota", + "NotificationOptionAudioPlaybackStopped": "Audioa gelditua", + "NotificationOptionAudioPlayback": "Audioa martxan", + "NotificationOptionApplicationUpdateInstalled": "Aplikazioaren eguneraketa instalatua", + "NotificationOptionApplicationUpdateAvailable": "Aplikazioaren eguneraketa eskuragarri", + "NewVersionIsAvailable": "Jellyfin zerbitzariaren bertsio berria deskargatzeko eskuragarri dago.", + "NameSeasonUnknown": "Denboraldi ezezaguna", + "NameSeasonNumber": "{0} Denboraldia", + "NameInstallFailed": "{0} instalazioak huts egin du", + "Music": "Musika", + "MixedContent": "Eduki mistoa", + "MessageServerConfigurationUpdated": "Zerbitzariaren konfigurazioa eguneratu da", + "MessageNamedServerConfigurationUpdatedWithValue": "Zerbitzariaren {0} konfigurazio atala eguneratu da", + "MessageApplicationUpdatedTo": "Jellyfin zerbitzaria {0}-ra eguneratu da", + "MessageApplicationUpdated": "Jellyfin zerbitzaria eguneratu da", + "Latest": "Azkena", + "LabelRunningTimeValue": "Iraupena: {0}", + "LabelIpAddressValue": "IP helbidea: {0}", + "ItemRemovedWithName": "{0} liburutegitik kendu da", + "ItemAddedWithName": "{0} liburutegira gehitu da", + "HomeVideos": "Etxeko bideoak", + "HeaderNextUp": "Hurrengoa", + "HeaderLiveTV": "Zuzeneko TB", + "HeaderFavoriteSongs": "Gogoko abestiak", + "HeaderFavoriteShows": "Gogoko serieak", + "HeaderFavoriteEpisodes": "Gogoko atalak", + "HeaderFavoriteArtists": "Gogoko artistak", + "HeaderFavoriteAlbums": "Gogoko albumak", + "Forced": "Behartuta", + "FailedLoginAttemptWithUserName": "{0}-tik saioa hasteak huts egin du", + "External": "Kanpokoa", + "DeviceOnlineWithName": "{0} konektatu da", + "DeviceOfflineWithName": "{0} deskonektatu da", + "ChapterNameValue": "{0} Kapitulua", + "CameraImageUploadedFrom": "{0}-tik kamera irudi berri bat igo da", + "AuthenticationSucceededWithUserName": "{0} ongi autentifikatu da", + "Application": "Aplikazioa", + "AppDeviceValues": "App: {0}, Gailua: {1}", + "HearingImpaired": "Entzumen urritasuna", + "ProviderValue": "Hornitzailea: {0}", + "TaskKeyframeExtractorDescription": "Bideo fitxategietako fotograma gakoak ateratzen ditu HLS erreprodukzio-zerrenda zehatzagoak sortzeko. Zeregin honek denbora asko iraun dezake.", + "HeaderRecordingGroups": "Grabaketa taldeak", + "Inherit": "Oinordetu", + "TaskOptimizeDatabaseDescription": "Datu-basea trinkotu eta bertatik espazioa askatzen du. Liburutegia eskaneatu ondoren edo datu-basean aldaketak egin ondoren ataza hau exekutatzeak errendimendua hobetu lezake.", + "TaskKeyframeExtractor": "Fotograma gakoen erauzgailua", + "TaskRefreshTrickplayImages": "Trickplay irudiak sortu", + "TaskRefreshTrickplayImagesDescription": "Bideoentzako trickplay aurrebistak sortzen ditu gaitutako liburutegietan.", + "TaskAudioNormalization": "Audio normalizazioa", + "TaskDownloadMissingLyrics": "Deskargatu falta diren letrak", + "TaskDownloadMissingLyricsDescription": "Deskargatu abestientzako letrak", + "TaskExtractMediaSegments": "Multimedia segmentuen eskaneoa", + "TaskCleanCollectionsAndPlaylistsDescription": "Jada existitzen ez diren bildumak eta erreprodukzio-zerrendak kentzen ditu.", + "TaskCleanCollectionsAndPlaylists": "Garbitu bildumak eta erreprodukzio-zerrendak", + "TaskExtractMediaSegmentsDescription": "Media segmentuak atera edo lortzen ditu MediaSegment gaituta duten pluginetik.", + "TaskMoveTrickplayImages": "Aldatu Trickplay irudien kokalekua", + "TaskMoveTrickplayImagesDescription": "Lehendik dauden trickplay fitxategiak liburutegiaren ezarpenen arabera mugitzen dira.", + "TaskAudioNormalizationDescription": "Audio normalizazio datuak lortzeko fitxategiak eskaneatzen ditu.", + "CleanupUserDataTaskDescription": "Gutxienez 90 egunez dagoeneko existitzen ez den multimediatik erabiltzaile-datu guztiak (ikusteko egoera, gogokoen egoera, etab.) garbitzen ditu.", + "CleanupUserDataTask": "Erabiltzaileen datuak garbitzeko zeregina" +} diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json new file mode 100644 index 00000000..90cd3a58 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/fa.json @@ -0,0 +1,139 @@ +{ + "Albums": "آلبوم‌ها", + "AppDeviceValues": "برنامه: {0} ، دستگاه: {1}", + "Application": "برنامه", + "Artists": "هنرمندان", + "AuthenticationSucceededWithUserName": "{0} با موفقیت تایید اعتبار شد", + "Books": "کتاب‌ها", + "CameraImageUploadedFrom": "یک عکس جدید از دوربین ارسال شده است {0}", + "Channels": "کانالها", + "ChapterNameValue": "قسمت {0}", + "Collections": "مجموعه‌ها", + "DeviceOfflineWithName": "ارتباط {0} قطع شد", + "DeviceOnlineWithName": "{0} متصل شد", + "FailedLoginAttemptWithUserName": "تلاش برای ورود از {0} ناموفق بود", + "Favorites": "مورد علاقه‌ها", + "Folders": "پوشه‌ها", + "Genres": "ژانرها", + "HeaderAlbumArtists": "هنرمندان آلبوم", + "HeaderContinueWatching": "ادامه تماشا", + "HeaderFavoriteAlbums": "آلبوم‌های مورد علاقه", + "HeaderFavoriteArtists": "هنرمندان مورد علاقه", + "HeaderFavoriteEpisodes": "قسمت‌های مورد علاقه", + "HeaderFavoriteShows": "سریال‌های مورد علاقه", + "HeaderFavoriteSongs": "آهنگ‌های مورد علاقه", + "HeaderLiveTV": "پخش زنده", + "HeaderNextUp": "قسمت بعدی", + "HeaderRecordingGroups": "گروه‌های ضبط", + "HomeVideos": "ویدیوهای خانگی", + "Inherit": "به ارث برده", + "ItemAddedWithName": "{0} به کتابخانه افزوده شد", + "ItemRemovedWithName": "{0} از کتابخانه حذف شد", + "LabelIpAddressValue": "آدرس آی پی: {0}", + "LabelRunningTimeValue": "زمان اجرا: {0}", + "Latest": "جدیدترین‌ها", + "MessageApplicationUpdated": "سرور Jellyfin بروزرسانی شد", + "MessageApplicationUpdatedTo": "سرور Jellyfin به نسخه {0} بروزرسانی شد", + "MessageNamedServerConfigurationUpdatedWithValue": "پکربندی بخش {0} سرور بروزرسانی شد", + "MessageServerConfigurationUpdated": "پیکربندی سرور بروزرسانی شد", + "MixedContent": "محتوای مخلوط", + "Movies": "فیلم ها", + "Music": "موسیقی", + "MusicVideos": "موزیک ویدیوها", + "NameInstallFailed": "{0} نصب با مشکل مواجه شد", + "NameSeasonNumber": "فصل {0}", + "NameSeasonUnknown": "فصل ناشناخته", + "NewVersionIsAvailable": "یک نسخه جدید Jellyfin برای بروزرسانی آماده می‌باشد.", + "NotificationOptionApplicationUpdateAvailable": "بروزرسانی برنامه موجود است", + "NotificationOptionApplicationUpdateInstalled": "بروزرسانی برنامه نصب شد", + "NotificationOptionAudioPlayback": "پخش صدا آغاز شد", + "NotificationOptionAudioPlaybackStopped": "پخش صدا متوقف شد", + "NotificationOptionCameraImageUploaded": "تصاویر دوربین آپلود شد", + "NotificationOptionInstallationFailed": "نصب ناموفق", + "NotificationOptionNewLibraryContent": "محتوای جدید افزوده شد", + "NotificationOptionPluginError": "خرابی افزونه", + "NotificationOptionPluginInstalled": "افزونه نصب شد", + "NotificationOptionPluginUninstalled": "افزونه حذف شد", + "NotificationOptionPluginUpdateInstalled": "بروزرسانی افزونه نصب شد", + "NotificationOptionServerRestartRequired": "شروع مجدد سرور نیاز است", + "NotificationOptionTaskFailed": "شکست وظیفه برنامه ریزی شده", + "NotificationOptionUserLockedOut": "کاربر قفل شد", + "NotificationOptionVideoPlayback": "پخش ویدیو آغاز شد", + "NotificationOptionVideoPlaybackStopped": "پخش ویدیو متوقف شد", + "Photos": "عکس‌ها", + "Playlists": "لیست‌های پخش", + "Plugin": "افزونه", + "PluginInstalledWithName": "{0} نصب شد", + "PluginUninstalledWithName": "{0} حذف شد", + "PluginUpdatedWithName": "{0} آپدیت شد", + "ProviderValue": "ارائه دهنده: {0}", + "ScheduledTaskFailedWithName": "{0} شکست خورد", + "ScheduledTaskStartedWithName": "{0} شروع شد", + "ServerNameNeedsToBeRestarted": "{0} نیاز به راه اندازی مجدد دارد", + "Shows": "سریال‌ها", + "Songs": "موسیقی‌ها", + "StartupEmbyServerIsLoading": "سرور Jellyfin در حال بارگیری است. لطفا کمی بعد دوباره تلاش کنید.", + "SubtitleDownloadFailureFromForItem": "بارگیری زیرنویس برای {1} از {0} شکست خورد", + "Sync": "همگام‌سازی", + "System": "سیستم", + "TvShows": "سریال‌های تلویزیونی", + "User": "کاربر", + "UserCreatedWithName": "کاربر {0} ایجاد شد", + "UserDeletedWithName": "کاربر {0} حذف شد", + "UserDownloadingItemWithValues": "{0} در حال بارگیری {1} می‌باشد", + "UserLockedOutWithName": "کاربر {0} قفل شده است", + "UserOfflineFromDevice": "ارتباط {0} از {1} قطع شد", + "UserOnlineFromDevice": "{0} از {1} آنلاین می‌باشد", + "UserPasswordChangedWithName": "گذرواژه برای کاربر {0} تغییر کرد", + "UserPolicyUpdatedWithName": "سیاست کاربری برای {0} بروزرسانی شد", + "UserStartedPlayingItemWithValues": "{0} در حال پخش {1} بر روی {2} است", + "UserStoppedPlayingItemWithValues": "{0} پخش {1} را بر روی {2} به پایان رساند", + "ValueHasBeenAddedToLibrary": "{0} به کتابخانه‌ی رسانه‌ی شما افزوده شد", + "ValueSpecialEpisodeName": "ویژه - {0}", + "VersionNumber": "نسخه {0}", + "TaskCleanTranscodeDescription": "فایل‌های کدگذاری که قدیمی‌تر از یک روز هستند را حذف می‌کند.", + "TaskCleanTranscode": "پاکسازی مسیر کد گذاری", + "TaskUpdatePluginsDescription": "دانلود و نصب به روز رسانی افزونه‌هایی که برای به روز رسانی خودکار پیکربندی شده‌اند.", + "TaskDownloadMissingSubtitlesDescription": "جستجوی زیرنویس‌های ناموجود در اینترنت بر اساس پیکربندی ابرداده‌ها.", + "TaskDownloadMissingSubtitles": "دانلود زیرنویس‌های ناموجود", + "TaskRefreshChannelsDescription": "اطلاعات کانال اینترنتی را تازه سازی می‌کند.", + "TaskRefreshChannels": "تازه سازی کانال‌ها", + "TaskUpdatePlugins": "به روز رسانی افزونه‌ها", + "TaskRefreshPeopleDescription": "ابرداده‌ها برای بازیگران و کارگردانان در کتابخانه رسانه شما به روزرسانی می شوند.", + "TaskRefreshPeople": "تازه سازی افراد", + "TaskCleanLogsDescription": "واقعه نگارهایی را که قدیمی تر {0} روز هستند را حذف می کند.", + "TaskCleanLogs": "پاکسازی مسیر واقعه نگار", + "TaskRefreshLibraryDescription": "کتابخانه رسانه شما را اسکن می‌کند و ابرداده‌ها را تازه سازی می‌کند.", + "TaskRefreshLibrary": "اسکن کتابخانه رسانه", + "TaskRefreshChapterImagesDescription": "عکس‌های کوچک برای ویدیوهایی که سکانس دارند ایجاد می‌کند.", + "TaskRefreshChapterImages": "استخراج عکس‌های سکانس", + "TaskCleanCacheDescription": "فایل‌های حافظه موقت که توسط سیستم دیگر مورد نیاز نیستند حذف می‌شوند.", + "TaskCleanCache": "پاکسازی مسیر حافظه موقت", + "TasksChannelsCategory": "کانال‌های داخلی", + "TasksApplicationCategory": "برنامه", + "TasksLibraryCategory": "کتابخانه", + "TasksMaintenanceCategory": "تعمیر", + "Forced": "اجباری", + "Default": "پیشفرض", + "TaskCleanActivityLogDescription": "ورودی‌های قدیمی‌تر از سن تنظیم شده در سیاهه فعالیت را حذف می‌کند.", + "TaskCleanActivityLog": "پاکسازی سیاهه فعالیت", + "Undefined": "تعریف نشده", + "TaskOptimizeDatabase": "بهینه سازی پایگاه داده", + "TaskOptimizeDatabaseDescription": "فشرده سازی پایگاه داده و باز کردن فضای آزاد.اجرای این گزینه بعد از اسکن کردن کتابخانه یا تغییرات دیگر که روی پایگاه داده تأثیر میگذارند میتواند کارایی را بهبود ببخشد.", + "TaskKeyframeExtractorDescription": "فریم های کلیدی را از فایل های ویدئویی استخراج می کند تا لیست های پخش HLS دقیق تری ایجاد کند. این کار ممکن است برای مدت طولانی اجرا شود.", + "TaskKeyframeExtractor": "استخراج کننده فریم کلیدی", + "External": "خارجی", + "HearingImpaired": "مشکل شنوایی", + "TaskRefreshTrickplayImages": "تولید تصاویر Trickplay", + "TaskRefreshTrickplayImagesDescription": "تولید پیش‌نمایش های trickplay برای ویدیو های فعال شده در کتابخانه.", + "TaskCleanCollectionsAndPlaylists": "پاکسازی مجموعه ها و لیست پخش", + "TaskCleanCollectionsAndPlaylistsDescription": "موارد را از مجموعه ها و لیست پخش هایی که دیگر وجود ندارند حذف میکند.", + "TaskAudioNormalizationDescription": "بررسی فایل برای داده‌های نرمال کردن صدا.", + "TaskDownloadMissingLyrics": "دانلود متن‌های ناموجود", + "TaskDownloadMissingLyricsDescription": "دانلود متن شعر‌ها", + "TaskAudioNormalization": "نرمال کردن صدا", + "TaskExtractMediaSegments": "بررسی بخش محتوا", + "TaskExtractMediaSegmentsDescription": "بخش‌های محتوا را از افزونه‌های مربوط استخراح می‌کند.", + "TaskMoveTrickplayImages": "جابه‌جایی عکس‌های Trickplay", + "TaskMoveTrickplayImagesDescription": "داده‌های Trickplay را با توجه به تنظیمات کتاب‌خانه جابه‌جا می‌کند." +} diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json new file mode 100644 index 00000000..15a04d22 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/fi.json @@ -0,0 +1,141 @@ +{ + "HeaderLiveTV": "Televisio", + "NewVersionIsAvailable": "Uusi versio Jellyfin-palvelimesta on ladattavissa.", + "NameSeasonUnknown": "Tuntematon kausi", + "NameSeasonNumber": "Kausi {0}", + "NameInstallFailed": "{0} asennus epäonnistui", + "MusicVideos": "Musiikkivideot", + "Music": "Musiikki", + "Movies": "Elokuvat", + "MixedContent": "Sekalainen sisältö", + "MessageServerConfigurationUpdated": "Palvelimen asetukset on päivitetty", + "MessageNamedServerConfigurationUpdatedWithValue": "Palvelimen asetusten osio {0} on päivitetty", + "MessageApplicationUpdatedTo": "Jellyfin-palvelin on päivitetty versioon {0}", + "MessageApplicationUpdated": "Jellyfin-palvelin on päivitetty", + "Latest": "Viimeisimmät", + "LabelRunningTimeValue": "Kesto: {0}", + "LabelIpAddressValue": "IP-osoite: {0}", + "ItemRemovedWithName": "{0} poistettiin kirjastosta", + "ItemAddedWithName": "{0} lisättiin kirjastoon", + "Inherit": "Peri", + "HomeVideos": "Kotivideot", + "HeaderRecordingGroups": "Tallennusryhmät", + "HeaderNextUp": "Seuraavaksi", + "HeaderFavoriteSongs": "Suosikkikappaleet", + "HeaderFavoriteShows": "Suosikkisarjat", + "HeaderFavoriteEpisodes": "Suosikkijaksot", + "HeaderFavoriteArtists": "Suosikkiesittäjät", + "HeaderFavoriteAlbums": "Suosikkialbumit", + "HeaderContinueWatching": "Jatka katselua", + "HeaderAlbumArtists": "Albumin esittäjät", + "Genres": "Tyylilajit", + "Folders": "Kansiot", + "Favorites": "Suosikit", + "FailedLoginAttemptWithUserName": "Epäonnistunut kirjautumisyritys lähteestä \"{0}\"", + "DeviceOnlineWithName": "{0} on yhdistetty", + "DeviceOfflineWithName": "{0} on katkaissut yhteyden", + "Collections": "Kokoelmat", + "ChapterNameValue": "Kappale {0}", + "Channels": "Kanavat", + "CameraImageUploadedFrom": "Uusi kameran kuva on sirretty lähteestä {0}", + "Books": "Kirjat", + "AuthenticationSucceededWithUserName": "{0} on todennettu", + "Artists": "Esittäjät", + "Application": "Sovellus", + "AppDeviceValues": "Sovellus: {0}, Laite: {1}", + "Albums": "Albumit", + "User": "Käyttäjä", + "System": "Järjestelmä", + "ScheduledTaskFailedWithName": "{0} epäonnistui", + "PluginUpdatedWithName": "{0} päivitettiin", + "PluginInstalledWithName": "{0} asennettiin", + "Photos": "Valokuvat", + "ScheduledTaskStartedWithName": "\"{0}\" käynnistetty", + "PluginUninstalledWithName": "{0} poistettiin", + "Playlists": "Soittolistat", + "VersionNumber": "Versio {0}", + "ValueSpecialEpisodeName": "Erikoisjakso - {0}", + "ValueHasBeenAddedToLibrary": "\"{0}\" on lisätty mediakirjastoon", + "UserStoppedPlayingItemWithValues": "{0} lopetti kohteen \"{1}\" toiston sijainnissa \"{2}\"", + "UserStartedPlayingItemWithValues": "{0} toistaa kohdetta \"{1}\" sijainnissa \"{2}\"", + "UserPolicyUpdatedWithName": "Käyttäjän {0} käyttöoikeudet on päivitetty", + "UserPasswordChangedWithName": "Käyttäjän {0} salasana on vaihdettu", + "UserOnlineFromDevice": "{0} on yhdistänyt sijainnista \"{1}\"", + "UserOfflineFromDevice": "{0} on katkaissut yhteyden sijainnista \"{1}\"", + "UserLockedOutWithName": "Käyttäjä {0} on lukittu", + "UserDownloadingItemWithValues": "{0} lataa kohdetta \"{1}\"", + "UserDeletedWithName": "Käyttäjä {0} on poistettu", + "UserCreatedWithName": "Käyttäjä {0} on luotu", + "TvShows": "Sarjat", + "Sync": "Synkronointi", + "SubtitleDownloadFailureFromForItem": "Tekstityksen lataus lähteestä \"{0}\" kohteelle \"{1}\" epäonnistui", + "StartupEmbyServerIsLoading": "Jellyfin-palvelin on latautumassa. Yritä hetken kuluttua uudelleen.", + "Songs": "Kappaleet", + "Shows": "Sarjat", + "ServerNameNeedsToBeRestarted": "\"{0}\" on käynnistettävä uudelleen", + "ProviderValue": "Lähde: {0}", + "Plugin": "Lisäosa", + "NotificationOptionVideoPlaybackStopped": "Videon toisto lopetettu", + "NotificationOptionVideoPlayback": "Videon toisto aloitettu", + "NotificationOptionUserLockedOut": "Käyttäjä on lukittu", + "NotificationOptionTaskFailed": "Ajoitettu tehtävä epäonnistui", + "NotificationOptionServerRestartRequired": "Palvelimen uudelleenkäynnistys vaaditaan", + "NotificationOptionPluginUpdateInstalled": "Lisäosa päivitettiin", + "NotificationOptionPluginUninstalled": "Lisäosa poistettiin", + "NotificationOptionPluginInstalled": "Lisäosa asennettiin", + "NotificationOptionPluginError": "Lisäosan virhe", + "NotificationOptionNewLibraryContent": "Sisältöä on lisätty", + "NotificationOptionInstallationFailed": "Asennus epäonnistui", + "NotificationOptionCameraImageUploaded": "Kameran kuva on tallennettu", + "NotificationOptionAudioPlaybackStopped": "Äänen toisto lopetettu", + "NotificationOptionAudioPlayback": "Äänen toisto aloitettu", + "NotificationOptionApplicationUpdateInstalled": "Sovelluspäivitys asennettiin", + "NotificationOptionApplicationUpdateAvailable": "Sovelluspäivitys on saatavilla", + "TasksMaintenanceCategory": "Ylläpito", + "TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä määritettyjen metatietoasetusten mukaisesti.", + "TaskDownloadMissingSubtitles": "Lataa puuttuvat tekstitykset", + "TaskRefreshChannelsDescription": "Päivittää internet-kanavien tiedot.", + "TaskRefreshChannels": "Päivitä kanavat", + "TaskCleanTranscodeDescription": "Poistaa päivää vanhemmat transkoodaustiedostot.", + "TaskCleanTranscode": "Puhdista transkoodauskansio", + "TaskUpdatePluginsDescription": "Lataa ja asentaa päivitykset lisäosille, jotka on määritetty päivittymään automaattisesti.", + "TaskUpdatePlugins": "Päivitä lisäosat", + "TaskRefreshPeopleDescription": "Päivittää mediakirjaston näyttelijöiden ja ohjaajien metatiedot.", + "TaskRefreshPeople": "Päivitä henkilöt", + "TaskCleanLogsDescription": "Poistaa {0} päivää vanhemmat lokitiedostot.", + "TaskCleanLogs": "Siivoa lokikansio", + "TaskRefreshLibraryDescription": "Tarkastaa mediakirjastosi sisällön uusien tiedostojen varalta ja päivittää metatiedot.", + "TaskRefreshLibrary": "Päivitä mediakirjasto", + "TaskRefreshChapterImagesDescription": "Luo esikatselukuvat videoille, jotka sisältävät kappalejaon.", + "TaskRefreshChapterImages": "Pura kappalejaon kuvat", + "TaskCleanCacheDescription": "Poistaa tarpeettomiksi jääneet väliaikaistiedostot.", + "TaskCleanCache": "Tyhjennä välimuistikansio", + "TasksChannelsCategory": "Internet-kanavat", + "TasksApplicationCategory": "Sovellus", + "TasksLibraryCategory": "Kirjasto", + "Forced": "Pakotettu", + "Default": "Oletus", + "TaskCleanActivityLogDescription": "Poistaa määritettyä ikää vanhemmat tapahtumat toimintahistoriasta.", + "TaskCleanActivityLog": "Tyhjennä toimintahistoria", + "Undefined": "Määrittelemätön", + "TaskOptimizeDatabaseDescription": "Tiivistää ja puhdistaa tietokannan. Tämän toiminnon suorittaminen kirjastopäivityksen tai muiden mahdollisten tietokantamuutosten jälkeen voi parantaa suorituskykyä.", + "TaskOptimizeDatabase": "Optimoi tietokanta", + "TaskKeyframeExtractorDescription": "Purkaa videotiedostojen avainkuvat tarkempien HLS-toistolistojen luomiseksi. Tehtävä saattaa kestää huomattavan pitkään.", + "TaskKeyframeExtractor": "Avainkuvien purkain", + "External": "Ulkoinen", + "HearingImpaired": "Kuulorajoitteinen", + "TaskRefreshTrickplayImages": "Luo Trickplay-kuvat", + "TaskRefreshTrickplayImagesDescription": "Luo Trickplay-esikatselut käytössä olevien kirjastojen videoista.", + "TaskCleanCollectionsAndPlaylistsDescription": "Poistaa kohteet kokoelmista ja soittolistoista joita ei ole enää olemassa.", + "TaskCleanCollectionsAndPlaylists": "Puhdista kokoelmat ja soittolistat", + "TaskAudioNormalization": "Äänenvoimakkuuden normalisointi", + "TaskAudioNormalizationDescription": "Etsii tiedostoista äänenvoimakkuuden normalisointitietoja.", + "TaskDownloadMissingLyrics": "Lataa puuttuva lyriikka", + "TaskExtractMediaSegments": "Mediasegmentin skannaus", + "TaskDownloadMissingLyricsDescription": "Ladataan sanoituksia", + "TaskExtractMediaSegmentsDescription": "Poimii tai hankkii mediasegmenttejä MediaSegment-yhteensopivista laajennuksista.", + "TaskMoveTrickplayImages": "Siirrä Trickplay-kuvien sijainti", + "TaskMoveTrickplayImagesDescription": "Siirtää olemassa olevia trickplay-tiedostoja kirjaston asetusten mukaan.", + "CleanupUserDataTask": "Käyttäjätietojen puhdistustehtävä", + "CleanupUserDataTaskDescription": "Puhdistaa kaikki käyttäjätiedot (katselutila, suosikit ym.) medioista, joita ei ole ollut saatavilla yli 90 päivään." +} diff --git a/Emby.Server.Implementations/Localization/Core/fil.json b/Emby.Server.Implementations/Localization/Core/fil.json new file mode 100644 index 00000000..28c1d2be --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/fil.json @@ -0,0 +1,129 @@ +{ + "VersionNumber": "Bersyon {0}", + "ValueSpecialEpisodeName": "Espesyal - {0}", + "ValueHasBeenAddedToLibrary": "Naidagdag na ang {0} sa iyong librerya ng medya", + "UserStoppedPlayingItemWithValues": "Natapos ni {0} ang {1} sa {2}", + "UserStartedPlayingItemWithValues": "Si {0} ay nagpla-play ng {1} sa {2}", + "UserPolicyUpdatedWithName": "Ang user policy ay nai-update para kay {0}", + "UserPasswordChangedWithName": "Napalitan na ang password ni {0}", + "UserOnlineFromDevice": "Si {0} ay naka-konekta galing sa {1}", + "UserOfflineFromDevice": "Si {0} ay na-diskonekta galing sa {1}", + "UserLockedOutWithName": "Si {0} ay nalock out", + "UserDownloadingItemWithValues": "Nagdadownload si {0} ng {1}", + "UserDeletedWithName": "Natanggal na is user {0}", + "UserCreatedWithName": "Nagawa na si user {0}", + "User": "User", + "TvShows": "Mga Palabas sa Telebisyon", + "System": "Sistema", + "Sync": "Pag-sync", + "SubtitleDownloadFailureFromForItem": "Hindi nai-download ang subtitles {0} para sa {1}", + "StartupEmbyServerIsLoading": "Naglo-load ang Jellyfin Server. Mangyaring subukan ulit sandali.", + "Songs": "Mga Kanta", + "Shows": "Mga Pelikula", + "ServerNameNeedsToBeRestarted": "Kailangan irestart ang {0}", + "ScheduledTaskStartedWithName": "Nagsimula na ang {0}", + "ScheduledTaskFailedWithName": "Hindi gumana ang {0}", + "ProviderValue": "Tagapagtustos: {0}", + "PluginUpdatedWithName": "Naiupdate na ang {0}", + "PluginUninstalledWithName": "Naiuninstall na ang {0}", + "PluginInstalledWithName": "Nainstall na ang {0}", + "Plugin": "Plugin", + "Playlists": "Mga Playlist", + "Photos": "Mga Larawan", + "NotificationOptionVideoPlaybackStopped": "Huminto na ang pelikula", + "NotificationOptionVideoPlayback": "Nagsimula na ang pelikula", + "NotificationOptionUserLockedOut": "Naka-lock out ang user", + "NotificationOptionTaskFailed": "Hindi gumana ang scheduled task", + "NotificationOptionServerRestartRequired": "Kailangan i-restart ang server", + "NotificationOptionPluginUpdateInstalled": "Nai-update na ang plugin", + "NotificationOptionPluginUninstalled": "Nai-uninstall na ang plugin", + "NotificationOptionPluginInstalled": "Nainstall na ang plugin", + "NotificationOptionPluginError": "Hindi gumagana ang plugin", + "NotificationOptionNewLibraryContent": "May bagong content na naidagdag", + "NotificationOptionInstallationFailed": "Hindi nainstall ng mabuti", + "NotificationOptionCameraImageUploaded": "Naiupload na ang litrato", + "NotificationOptionAudioPlaybackStopped": "Huminto na ang patugtog", + "NotificationOptionAudioPlayback": "Nagsimula na ang patugtog", + "NotificationOptionApplicationUpdateInstalled": "Naiupdate na ang aplikasyon", + "NotificationOptionApplicationUpdateAvailable": "May bagong update ang aplikasyon", + "NewVersionIsAvailable": "May bagong version ng Jellyfin Server na pwede i-download.", + "NameSeasonUnknown": "Hindi matukoy ang season", + "NameSeasonNumber": "Season {0}", + "NameInstallFailed": "Hindi nainstall ang {0}", + "MusicVideos": "Mga Music video", + "Music": "Mga Kanta", + "Movies": "Mga Pelikula", + "MixedContent": "Halo-halong content", + "MessageServerConfigurationUpdated": "Naiupdate na ang server configuration", + "MessageNamedServerConfigurationUpdatedWithValue": "Naiupdate na ang server configuration section {0}", + "MessageApplicationUpdatedTo": "Ang bersyon ng Jellyfin Server ay naiupdate sa {0}", + "MessageApplicationUpdated": "Naiupdate na ang Jellyfin Server", + "Latest": "Pinakabago", + "LabelRunningTimeValue": "Oras: {0}", + "LabelIpAddressValue": "IP address: {0}", + "ItemRemovedWithName": "Naitanggal ang {0} sa librerya", + "ItemAddedWithName": "Naidagdag ang {0} sa librerya", + "Inherit": "Manahin", + "HeaderRecordingGroups": "Pagtatalang Grupo", + "HeaderNextUp": "Susunod", + "HeaderLiveTV": "Live TV", + "HeaderFavoriteSongs": "Mga Paboritong Kanta", + "HeaderFavoriteShows": "Mga Paboritong Pelikula", + "HeaderFavoriteEpisodes": "Mga Paboritong Yugto", + "HeaderFavoriteArtists": "Mga Paboritong Artista", + "HeaderFavoriteAlbums": "Mga Paboritong Album", + "HeaderContinueWatching": "Magpatuloy sa Panonood", + "HeaderAlbumArtists": "Mga Artista ng Album", + "Genres": "Mga Kategorya", + "Folders": "Mga Folder", + "Favorites": "Mga Paborito", + "FailedLoginAttemptWithUserName": "Maling login galing kay/sa {0}", + "DeviceOnlineWithName": "Nakakonekta si/ang {0}", + "DeviceOfflineWithName": "Nadiskonekta si/ang {0}", + "Collections": "Mga Koleksyon", + "ChapterNameValue": "Kabanata {0}", + "Channels": "Mga Channel", + "CameraImageUploadedFrom": "May bagong larawan na naupload galing sa/kay {0}", + "Books": "Mga Libro", + "AuthenticationSucceededWithUserName": "Napatunayan si/ang {0}", + "Artists": "Mga Artista", + "Application": "Aplikasyon", + "AppDeviceValues": "Aplikasyon: {0}, Aparato: {1}", + "Albums": "Mga Album", + "TaskRefreshLibrary": "Suriin and Librerya ng Medya", + "TaskRefreshChapterImagesDescription": "Gumawa ng larawan para sa mga pelikula na may kabanata.", + "TaskRefreshChapterImages": "Kunin ang mga larawan ng kabanata", + "TaskCleanCacheDescription": "Tanggalin ang mga cache file na hindi na kailangan ng sistema.", + "TasksChannelsCategory": "Palabas sa internet", + "TasksLibraryCategory": "Librerya", + "TasksMaintenanceCategory": "Pagpapanatili", + "HomeVideos": "Sariling video/pelikula", + "TaskRefreshPeopleDescription": "Ini-update ang metadata para sa mga aktor at direktor sa iyong librerya ng medya.", + "TaskRefreshPeople": "I-refresh ang Tauhan", + "TaskDownloadMissingSubtitlesDescription": "Hinahanap sa internet ang mga nawawalang subtiles base sa metadata configuration.", + "TaskDownloadMissingSubtitles": "I-download and nawawalang subtitles", + "TaskRefreshChannelsDescription": "Ni-rerefresh ang impormasyon sa internet channels.", + "TaskRefreshChannels": "I-refresh ang Channels", + "TaskCleanTranscodeDescription": "Binubura ang transcode files na mas matanda ng isang araw.", + "TaskUpdatePluginsDescription": "Nag download at install ng updates sa plugins na naka configure para sa awtomatikong pag-update.", + "TaskUpdatePlugins": "I-update ang Plugins", + "TaskCleanLogsDescription": "Binubura and files ng talaan na mas mantanda ng {0} araw.", + "TaskCleanTranscode": "Linisin and Direktoryo ng Transcode", + "TaskCleanLogs": "Linisin and Direktoryo ng Talaan", + "TaskRefreshLibraryDescription": "Sinusuri ang iyong librerya ng medya para sa bagong files at irefresh ang metadata.", + "TaskCleanCache": "Linisin and Direktoryo ng Cache", + "TasksApplicationCategory": "Aplikasyon", + "TaskCleanActivityLog": "Linisin ang Tala ng Aktibidad", + "TaskCleanActivityLogDescription": "Tanggalin ang mga tala ng aktibidad na mas luma sa nakatakda na edad.", + "Default": "Default", + "Undefined": "Hindi tiyak", + "Forced": "Sapilitan", + "TaskOptimizeDatabaseDescription": "Iko-compact ang database at ita-truncate ang free space. Ang pagpapatakbo ng gawaing ito pagkatapos ng pag-scan sa library o paggawa ng iba pang mga pagbabago na nagpapahiwatig ng mga pagbabago sa database ay maaaring magpa-improve ng performance.", + "TaskOptimizeDatabase": "I-optimize ang database", + "HearingImpaired": "Bingi", + "TaskKeyframeExtractor": "Tagabunot ng Keyframe", + "TaskKeyframeExtractorDescription": "Nagbubunot ng keyframe mula sa mga bidyo upang makabuo ng mas tumpak na HLS playlist. Maaaring matagal ito gawin.", + "External": "External", + "TaskRefreshTrickplayImages": "Gumawa ng Trickplay na Imahe", + "TaskRefreshTrickplayImagesDescription": "Nanggagawa ng mga trickplay prebiyu para sa mga bidyo sa pinaganang mga aklatan." +} diff --git a/Emby.Server.Implementations/Localization/Core/fo.json b/Emby.Server.Implementations/Localization/Core/fo.json new file mode 100644 index 00000000..40aa5f71 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/fo.json @@ -0,0 +1,18 @@ +{ + "Artists": "Listafólk", + "Collections": "Søvn", + "Default": "Sjálvgildi", + "DeviceOfflineWithName": "{0} hevur slitið sambandið", + "External": "Ytri", + "Genres": "Greinar", + "Albums": "Album", + "AppDeviceValues": "App: {0}, Eind: {1}", + "Application": "Nýtsluskipan", + "Books": "Bøkur", + "Channels": "Rásir", + "ChapterNameValue": "Kapittul {0}", + "DeviceOnlineWithName": "{0} er sambundið", + "Favorites": "Yndis", + "Folders": "Mappur", + "Forced": "Kravt" +} diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json new file mode 100644 index 00000000..a8964e8b --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json @@ -0,0 +1,141 @@ +{ + "Albums": "Albums", + "AppDeviceValues": "App : {0}, Appareil : {1}", + "Application": "Application", + "Artists": "Artistes", + "AuthenticationSucceededWithUserName": "{0} authentifié avec succès", + "Books": "Livres", + "CameraImageUploadedFrom": "Une nouvelle photo a été téléversée depuis {0}", + "Channels": "Chaînes", + "ChapterNameValue": "Chapitre {0}", + "Collections": "Collections", + "DeviceOfflineWithName": "{0} s'est déconnecté", + "DeviceOnlineWithName": "{0} est connecté", + "FailedLoginAttemptWithUserName": "Tentative de connexion échouée par {0}", + "Favorites": "Favoris", + "Folders": "Dossiers", + "Genres": "Genres", + "HeaderAlbumArtists": "Artistes de l'album", + "HeaderContinueWatching": "Reprendre le visionnement", + "HeaderFavoriteAlbums": "Albums favoris", + "HeaderFavoriteArtists": "Artistes favoris", + "HeaderFavoriteEpisodes": "Épisodes favoris", + "HeaderFavoriteShows": "Séries favorites", + "HeaderFavoriteSongs": "Chansons favorites", + "HeaderLiveTV": "TV en direct", + "HeaderNextUp": "À Suivre", + "HeaderRecordingGroups": "Groupes d'enregistrements", + "HomeVideos": "Vidéos personnelles", + "Inherit": "Hérite", + "ItemAddedWithName": "{0} a été ajouté à la médiathèque", + "ItemRemovedWithName": "{0} a été supprimé de la médiathèque", + "LabelIpAddressValue": "Adresse IP : {0}", + "LabelRunningTimeValue": "Durée : {0}", + "Latest": "Plus récent", + "MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour", + "MessageApplicationUpdatedTo": "Le serveur Jellyfin a été mis à jour vers la version {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "La configuration de la section {0} du serveur a été mise à jour", + "MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour", + "MixedContent": "Contenu mixte", + "Movies": "Films", + "Music": "Musique", + "MusicVideos": "Vidéoclips", + "NameInstallFailed": "échec d'installation de {0}", + "NameSeasonNumber": "Saison {0}", + "NameSeasonUnknown": "Saison Inconnue", + "NewVersionIsAvailable": "Une nouvelle version du serveur Jellyfin est disponible.", + "NotificationOptionApplicationUpdateAvailable": "Mise à jour de l'application disponible", + "NotificationOptionApplicationUpdateInstalled": "Mise à jour de l'application installée", + "NotificationOptionAudioPlayback": "Lecture audio démarrée", + "NotificationOptionAudioPlaybackStopped": "Lecture audio arrêtée", + "NotificationOptionCameraImageUploaded": "Image d'appareil photo transférée", + "NotificationOptionInstallationFailed": "Échec d'installation", + "NotificationOptionNewLibraryContent": "Nouveau contenu ajouté", + "NotificationOptionPluginError": "Erreur d'extension", + "NotificationOptionPluginInstalled": "Extension installée", + "NotificationOptionPluginUninstalled": "Extension désinstallée", + "NotificationOptionPluginUpdateInstalled": "Mise à jour d'extension installée", + "NotificationOptionServerRestartRequired": "Un redémarrage du serveur est requis", + "NotificationOptionTaskFailed": "Échec de tâche planifiée", + "NotificationOptionUserLockedOut": "Utilisateur verrouillé", + "NotificationOptionVideoPlayback": "Lecture vidéo démarrée", + "NotificationOptionVideoPlaybackStopped": "Lecture vidéo arrêtée", + "Photos": "Photos", + "Playlists": "Listes de lecture", + "Plugin": "Extension", + "PluginInstalledWithName": "{0} a été installé", + "PluginUninstalledWithName": "{0} a été désinstallé", + "PluginUpdatedWithName": "{0} a été mis à jour", + "ProviderValue": "Fournisseur : {0}", + "ScheduledTaskFailedWithName": "{0} a échoué", + "ScheduledTaskStartedWithName": "{0} a commencé", + "ServerNameNeedsToBeRestarted": "{0} doit être redémarré", + "Shows": "Séries", + "Songs": "Chansons", + "StartupEmbyServerIsLoading": "Serveur Jellyfin en cours de chargement. Réessayez dans quelques instants.", + "SubtitleDownloadFailureFromForItem": "Échec du téléchargement des sous-titres depuis {0} pour {1}", + "Sync": "Synchroniser", + "System": "Système", + "TvShows": "Séries Télé", + "User": "Utilisateur", + "UserCreatedWithName": "L'utilisateur {0} a été créé", + "UserDeletedWithName": "L'utilisateur {0} supprimé", + "UserDownloadingItemWithValues": "{0} télécharge {1}", + "UserLockedOutWithName": "L'utilisateur {0} a été verrouillé", + "UserOfflineFromDevice": "{0} s'est déconnecté de {1}", + "UserOnlineFromDevice": "{0} s'est connecté de {1}", + "UserPasswordChangedWithName": "Le mot de passe de utilisateur {0} a été modifié", + "UserPolicyUpdatedWithName": "La politique de l'utilisateur a été mise à jour pour {0}", + "UserStartedPlayingItemWithValues": "{0} joue {1} sur {2}", + "UserStoppedPlayingItemWithValues": "{0} a terminé la lecture de {1} sur {2}", + "ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque", + "ValueSpecialEpisodeName": "Spécial - {0}", + "VersionNumber": "Version {0}", + "TasksLibraryCategory": "Médiathèque", + "TasksMaintenanceCategory": "Entretien", + "TaskDownloadMissingSubtitlesDescription": "Recherche les sous-titres manquant sur l'internet selon la configuration des métadonnées.", + "TaskDownloadMissingSubtitles": "Télécharger les sous-titres manquants", + "TaskRefreshChannelsDescription": "Rafraîchit les informations des chaines internet.", + "TaskRefreshChannels": "Rafraîchir les chaines", + "TaskCleanTranscodeDescription": "Supprime les fichiers de transcodage datant de plus d'un jour.", + "TaskCleanTranscode": "Nettoyer le répertoire de transcodage", + "TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des extensions configurés pour les m.à.j. automatiques.", + "TaskUpdatePlugins": "Mise à jour des extensions", + "TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et réalisateurs dans votre médiathèque.", + "TaskRefreshPeople": "Rafraîchir les personnes", + "TaskCleanLogsDescription": "Supprime les journaux plus vieux que {0} jours.", + "TaskCleanLogs": "Nettoyer le répertoire des journaux", + "TaskRefreshLibraryDescription": "Analyse votre médiathèque pour trouver de nouveaux fichiers et rafraîchit les métadonnées.", + "TaskRefreshChapterImages": "Extraire les images de chapitre", + "TaskRefreshChapterImagesDescription": "Créer des vignettes pour les vidéos qui ont des chapitres.", + "TaskRefreshLibrary": "Analyser la médiathèque", + "TaskCleanCache": "Nettoyer le répertoire des fichiers temporaires", + "TasksApplicationCategory": "Application", + "TaskCleanCacheDescription": "Supprime les fichiers temporaires qui ne sont plus nécessaire pour le système.", + "TasksChannelsCategory": "Chaines Internet", + "Default": "Par défaut", + "TaskCleanActivityLogDescription": "Éfface les entrées du journal plus anciennes que l'âge configuré.", + "TaskCleanActivityLog": "Nettoyer le journal d'activité", + "Undefined": "Indéfini", + "Forced": "Forcé", + "TaskOptimizeDatabaseDescription": "Compacte la base de données et tronque l'espace libre. Lancer cette tâche après avoir scanné la bibliothèque ou faire d'autres changements impliquant des modifications de la base peuvent ameliorer les performances.", + "TaskOptimizeDatabase": "Optimiser la base de données", + "TaskKeyframeExtractorDescription": "Extrait les images clés des fichiers vidéo pour créer des listes de lecture HLS plus précises. Cette tâche peut durer très longtemps.", + "TaskKeyframeExtractor": "Extracteur d'image clé", + "External": "Externe", + "HearingImpaired": "Malentendants", + "TaskRefreshTrickplayImages": "Générer des images Trickplay", + "TaskRefreshTrickplayImagesDescription": "Crée des aperçus Trickplay pour les vidéos dans les médiathèques activées.", + "TaskCleanCollectionsAndPlaylists": "Nettoyer les collections et les listes de lecture", + "TaskCleanCollectionsAndPlaylistsDescription": "Supprime les éléments des collections et des listes de lecture qui n'existent plus.", + "TaskAudioNormalization": "Normalisation audio", + "TaskAudioNormalizationDescription": "Analyse les fichiers à la recherche de données de normalisation audio.", + "TaskExtractMediaSegments": "Analyse des segments de média", + "TaskDownloadMissingLyricsDescription": "Téléchargement des paroles des chansons", + "TaskMoveTrickplayImagesDescription": "Déplace les fichiers trickplay existants en fonction des paramètres de la bibliothèque.", + "TaskDownloadMissingLyrics": "Télécharger les paroles des chansons manquantes", + "TaskMoveTrickplayImages": "Changer l'emplacement des images Trickplay", + "TaskExtractMediaSegmentsDescription": "Extrait ou obtient des segments de média à partir des plugins compatibles avec MediaSegment.", + "CleanupUserDataTaskDescription": "Nettoie toutes les données utilisateur (état de la montre, statut favori, etc.) des supports qui ne sont plus présents depuis au moins 90 jours.", + "CleanupUserDataTask": "Tâche de nettoyage des données utilisateur" +} diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json new file mode 100644 index 00000000..b2a2e502 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -0,0 +1,141 @@ +{ + "Albums": "Albums", + "AppDeviceValues": "Application : {0}, Appareil : {1}", + "Application": "Application", + "Artists": "Artistes", + "AuthenticationSucceededWithUserName": "{0} authentifié avec succès", + "Books": "Livres", + "CameraImageUploadedFrom": "Une photo a été téléchargée depuis {0}", + "Channels": "Chaînes", + "ChapterNameValue": "Chapitre {0}", + "Collections": "Collections", + "DeviceOfflineWithName": "{0} s'est déconnecté", + "DeviceOnlineWithName": "{0} est connecté", + "FailedLoginAttemptWithUserName": "Échec de connexion depuis {0}", + "Favorites": "Favoris", + "Folders": "Dossiers", + "Genres": "Genres", + "HeaderAlbumArtists": "Artistes d'albums", + "HeaderContinueWatching": "Continuer de regarder", + "HeaderFavoriteAlbums": "Albums favoris", + "HeaderFavoriteArtists": "Artistes préférés", + "HeaderFavoriteEpisodes": "Épisodes favoris", + "HeaderFavoriteShows": "Séries favorites", + "HeaderFavoriteSongs": "Chansons préférées", + "HeaderLiveTV": "TV en direct", + "HeaderNextUp": "Prochain à venir", + "HeaderRecordingGroups": "Groupes d'enregistrements", + "HomeVideos": "Vidéos personnelles", + "Inherit": "Hériter", + "ItemAddedWithName": "{0} a été ajouté à la médiathèque", + "ItemRemovedWithName": "{0} a été supprimé de la médiathèque", + "LabelIpAddressValue": "Adresse IP : {0}", + "LabelRunningTimeValue": "Durée : {0}", + "Latest": "Derniers", + "MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour", + "MessageApplicationUpdatedTo": "Le serveur Jellyfin a été mis à jour en version {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "La configuration de la section {0} du serveur a été mise à jour", + "MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour", + "MixedContent": "Contenu mixte", + "Movies": "Films", + "Music": "Musique", + "MusicVideos": "Clips musicaux", + "NameInstallFailed": "{0} échec de l'installation", + "NameSeasonNumber": "Saison {0}", + "NameSeasonUnknown": "Saison inconnue", + "NewVersionIsAvailable": "Une nouvelle version de Jellyfin Serveur est disponible au téléchargement.", + "NotificationOptionApplicationUpdateAvailable": "Mise à jour de l'application disponible", + "NotificationOptionApplicationUpdateInstalled": "Mise à jour de l'application installée", + "NotificationOptionAudioPlayback": "Lecture audio démarrée", + "NotificationOptionAudioPlaybackStopped": "Lecture audio arrêtée", + "NotificationOptionCameraImageUploaded": "L'image de l'appareil photo a été téléversée", + "NotificationOptionInstallationFailed": "Échec de l'installation", + "NotificationOptionNewLibraryContent": "Nouveau contenu ajouté", + "NotificationOptionPluginError": "Erreur d'extension", + "NotificationOptionPluginInstalled": "Extension installée", + "NotificationOptionPluginUninstalled": "Extension désinstallée", + "NotificationOptionPluginUpdateInstalled": "Mise à jour d'extension installée", + "NotificationOptionServerRestartRequired": "Un redémarrage du serveur est requis", + "NotificationOptionTaskFailed": "Échec de tâche planifiée", + "NotificationOptionUserLockedOut": "Utilisateur verrouillé", + "NotificationOptionVideoPlayback": "Lecture vidéo démarrée", + "NotificationOptionVideoPlaybackStopped": "Lecture vidéo arrêtée", + "Photos": "Photos", + "Playlists": "Listes de lecture", + "Plugin": "Extension", + "PluginInstalledWithName": "{0} a été installé", + "PluginUninstalledWithName": "{0} a été désinstallé", + "PluginUpdatedWithName": "{0} a été mis à jour", + "ProviderValue": "Fournisseur : {0}", + "ScheduledTaskFailedWithName": "{0} a échoué", + "ScheduledTaskStartedWithName": "{0} a démarré", + "ServerNameNeedsToBeRestarted": "{0} doit être redémarré", + "Shows": "Séries", + "Songs": "Chansons", + "StartupEmbyServerIsLoading": "Le serveur Jellyfin est en cours de chargement. Veuillez réessayer dans quelques instants.", + "SubtitleDownloadFailureFromForItem": "Échec du téléchargement des sous-titres depuis {0} pour {1}", + "Sync": "Synchroniser", + "System": "Système", + "TvShows": "Séries TV", + "User": "Utilisateur", + "UserCreatedWithName": "L'utilisateur {0} a été créé", + "UserDeletedWithName": "L'utilisateur {0} a été supprimé", + "UserDownloadingItemWithValues": "{0} est en train de télécharger {1}", + "UserLockedOutWithName": "L'utilisateur {0} a été verrouillé", + "UserOfflineFromDevice": "{0} s'est déconnecté depuis {1}", + "UserOnlineFromDevice": "{0} s'est connecté depuis {1}", + "UserPasswordChangedWithName": "Le mot de passe pour l'utilisateur {0} a été modifié", + "UserPolicyUpdatedWithName": "La politique de l'utilisateur a été mise à jour pour {0}", + "UserStartedPlayingItemWithValues": "{0} est en train de lire {1} sur {2}", + "UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}", + "ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque", + "ValueSpecialEpisodeName": "Spécial - {0}", + "VersionNumber": "Version {0}", + "TasksChannelsCategory": "Chaînes en ligne", + "TaskDownloadMissingSubtitlesDescription": "Recherche les sous-titres manquants sur Internet en se basant sur la configuration des métadonnées.", + "TaskDownloadMissingSubtitles": "Télécharger les sous-titres manquants", + "TaskRefreshChannelsDescription": "Actualise les informations des chaînes en ligne.", + "TaskRefreshChannels": "Actualiser les chaînes", + "TaskCleanTranscodeDescription": "Supprime les fichiers transcodés de plus d'un jour.", + "TaskCleanTranscode": "Nettoyer le dossier des transcodages", + "TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des extensions configurées pour être mises à jour automatiquement.", + "TaskUpdatePlugins": "Mettre à jour les extensions", + "TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et réalisateurs dans votre médiathèque.", + "TaskRefreshPeople": "Actualiser les acteurs", + "TaskCleanLogsDescription": "Supprime les journaux de plus de {0} jours.", + "TaskCleanLogs": "Nettoyer le répertoire des journaux", + "TaskRefreshLibraryDescription": "Analyse votre médiathèque pour trouver de nouveaux fichiers et actualise les métadonnées.", + "TaskRefreshLibrary": "Analyser la médiathèque", + "TaskRefreshChapterImagesDescription": "Crée des vignettes pour les vidéos ayant des chapitres.", + "TaskRefreshChapterImages": "Extraire les images de chapitre", + "TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.", + "TaskCleanCache": "Vider le répertoire cache", + "TasksApplicationCategory": "Application", + "TasksLibraryCategory": "Médiathèque", + "TasksMaintenanceCategory": "Maintenance", + "TaskCleanActivityLogDescription": "Supprime les entrées du journal d'activité antérieures à l'âge configuré.", + "TaskCleanActivityLog": "Nettoyer le journal d'activité", + "Undefined": "Non défini", + "Forced": "Forcé", + "Default": "Par défaut", + "TaskOptimizeDatabaseDescription": "Réduit les espaces vides ou inutiles et compacte la base de données. Utiliser cette fonction après une mise à jour de la médiathèque ou toute autre modification de la base de données peut améliorer les performances du serveur.", + "TaskOptimizeDatabase": "Optimiser la base de données", + "TaskKeyframeExtractorDescription": "Extrait les images clés des fichiers vidéo pour créer des listes de lecture HLS plus précises. Cette tâche peut durer très longtemps.", + "TaskKeyframeExtractor": "Extracteur d'images clés", + "External": "Externe", + "HearingImpaired": "Malentendants", + "TaskRefreshTrickplayImages": "Générer des images Trickplay", + "TaskRefreshTrickplayImagesDescription": "Crée des aperçus Trickplay pour les vidéos dans les médiathèques activées.", + "TaskCleanCollectionsAndPlaylists": "Nettoyer les collections et les listes de lecture", + "TaskCleanCollectionsAndPlaylistsDescription": "Supprime les éléments des collections et des listes de lecture qui n'existent plus.", + "TaskAudioNormalization": "Normalisation audio", + "TaskAudioNormalizationDescription": "Analyse les fichiers à la recherche de données de normalisation audio.", + "TaskDownloadMissingLyricsDescription": "Téléchargement des paroles des chansons", + "TaskDownloadMissingLyrics": "Télécharger les paroles des chansons manquantes", + "TaskExtractMediaSegments": "Analyse des segments de média", + "TaskMoveTrickplayImages": "Changer l'emplacement des images Trickplay", + "TaskExtractMediaSegmentsDescription": "Extrait ou obtient des segments de média à partir des plugins compatibles avec MediaSegment.", + "TaskMoveTrickplayImagesDescription": "Déplace les fichiers trickplay existants en fonction des paramètres de la bibliothèque.", + "CleanupUserDataTaskDescription": "Nettoie toutes les données utilisateur (état de la montre, statut favori, etc.) des supports qui ne sont plus présents depuis au moins 90 jours.", + "CleanupUserDataTask": "Tâche de nettoyage des données utilisateur" +} diff --git a/Emby.Server.Implementations/Localization/Core/ga.json b/Emby.Server.Implementations/Localization/Core/ga.json new file mode 100644 index 00000000..8c0ae892 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ga.json @@ -0,0 +1,141 @@ +{ + "Albums": "Albaim", + "Artists": "Ealaíontóirí", + "AuthenticationSucceededWithUserName": "D'éirigh le fíordheimhniú {0}", + "Books": "Leabhair", + "CameraImageUploadedFrom": "Uaslódáladh íomhá ceamara nua ó {0}", + "Channels": "Cainéil", + "ChapterNameValue": "Caibidil {0}", + "Collections": "Bailiúcháin", + "Default": "Réamhshocrú", + "DeviceOfflineWithName": "Tá {0} dícheangailte", + "DeviceOnlineWithName": "Tá {0} nasctha", + "External": "Seachtrach", + "FailedLoginAttemptWithUserName": "Theip ar iarracht logáil isteach ó {0}", + "Favorites": "Ceanáin", + "TaskExtractMediaSegments": "Scanadh Deighleog na Meán", + "TaskMoveTrickplayImages": "Imirce Suíomh Íomhá Trickplay", + "TaskDownloadMissingLyrics": "Íosluchtaigh liricí ar iarraidh", + "TaskKeyframeExtractor": "Keyframe Eastarraingteoir", + "TaskAudioNormalization": "Normalú Fuaime", + "TaskAudioNormalizationDescription": "Scanann comhaid le haghaidh sonraí normalaithe fuaime.", + "TaskRefreshLibraryDescription": "Déanann sé do leabharlann meán a scanadh le haghaidh comhaid nua agus athnuachana meiteashonraí.", + "TaskCleanLogs": "Eolaire Logchomhad Glan", + "TaskCleanLogsDescription": "Scriostar comhaid loga atá níos mó ná {0} lá d'aois.", + "TaskRefreshPeopleDescription": "Nuashonraítear meiteashonraí d’aisteoirí agus stiúrthóirí i do leabharlann meán.", + "TaskRefreshTrickplayImages": "Gin Íomhánna Trickplay", + "TaskRefreshTrickplayImagesDescription": "Cruthaíonn sé réamhamhairc trickplay le haghaidh físeáin i leabharlanna cumasaithe.", + "TaskRefreshChannels": "Cainéil Athnuaigh", + "TaskRefreshChannelsDescription": "Athnuachan eolas faoi chainéil idirlín.", + "TaskOptimizeDatabase": "Bunachar sonraí a bharrfheabhsú", + "TaskKeyframeExtractorDescription": "Baintear eochairfhrámaí as comhaid físe chun seinmliostaí HLS níos cruinne a chruthú. Féadfaidh an tasc seo a bheith ar siúl ar feadh i bhfad.", + "TaskCleanCollectionsAndPlaylistsDescription": "Baintear míreanna as bailiúcháin agus seinmliostaí nach ann dóibh a thuilleadh.", + "TaskDownloadMissingLyricsDescription": "Íosluchtaigh liricí do na hamhráin", + "TaskUpdatePluginsDescription": "Íoslódálann agus suiteálann nuashonruithe do bhreiseáin atá cumraithe le nuashonrú go huathoibríoch.", + "TaskDownloadMissingSubtitlesDescription": "Déanann sé cuardach ar an idirlíon le haghaidh fotheidil atá ar iarraidh bunaithe ar chumraíocht meiteashonraí.", + "TaskExtractMediaSegmentsDescription": "Sliocht nó faigheann codanna meán ó bhreiseáin chumasaithe MediaSegment.", + "TaskCleanCollectionsAndPlaylists": "Glan suas bailiúcháin agus seinmliostaí", + "TaskOptimizeDatabaseDescription": "Comhdhlúthaíonn bunachar sonraí agus gearrtar spás saor in aisce. Má ritheann tú an tasc seo tar éis scanadh a dhéanamh ar an leabharlann nó athruithe eile a dhéanamh a thugann le tuiscint gur cheart go bhfeabhsófaí an fheidhmíocht.", + "TaskMoveTrickplayImagesDescription": "Bogtar comhaid trickplay atá ann cheana de réir socruithe na leabharlainne.", + "AppDeviceValues": "Aip: {0}, Gléas: {1}", + "Application": "Feidhmchlár", + "Folders": "Fillteáin", + "Forced": "Éigean", + "Genres": "Seánraí", + "HeaderAlbumArtists": "Ealaíontóirí albam", + "HeaderContinueWatching": "Leanúint ar aghaidh ag Breathnú", + "HeaderFavoriteAlbums": "Albam is fearr leat", + "HeaderFavoriteArtists": "Ealaíontóirí is Fearr", + "HeaderFavoriteEpisodes": "Eipeasóid is fearr leat", + "HeaderFavoriteShows": "Seónna is Fearr", + "HeaderFavoriteSongs": "Amhráin is fearr leat", + "HeaderLiveTV": "Teilifís beo", + "HeaderNextUp": "Ar Aghaidh Suas", + "HeaderRecordingGroups": "Grúpaí Taifeadta", + "HearingImpaired": "Lag éisteachta", + "HomeVideos": "Físeáin Baile", + "Inherit": "Oidhreacht", + "ItemAddedWithName": "Cuireadh {0} leis an leabharlann", + "ItemRemovedWithName": "Baineadh {0} den leabharlann", + "LabelIpAddressValue": "Seoladh IP: {0}", + "LabelRunningTimeValue": "Am rite: {0}", + "Latest": "Is déanaí", + "MessageApplicationUpdated": "Tá Freastalaí Jellyfin nuashonraithe", + "MessageApplicationUpdatedTo": "Nuashonraíodh Freastalaí Jellyfin go {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Nuashonraíodh an chuid cumraíochta freastalaí {0}", + "MessageServerConfigurationUpdated": "Nuashonraíodh cumraíocht an fhreastalaí", + "MixedContent": "Ábhar measctha", + "Movies": "Scannáin", + "Music": "Ceol", + "MusicVideos": "Físeáin Ceoil", + "NameInstallFailed": "Theip ar shuiteáil {0}", + "NameSeasonNumber": "Séasúr {0}", + "NameSeasonUnknown": "Séasúr Anaithnid", + "NewVersionIsAvailable": "Tá leagan nua de Jellyfin Server ar fáil le híoslódáil.", + "NotificationOptionApplicationUpdateAvailable": "Nuashonrú feidhmchláir ar fáil", + "NotificationOptionApplicationUpdateInstalled": "Nuashonrú feidhmchláir suiteáilte", + "NotificationOptionAudioPlayback": "Cuireadh tús le hathsheinm fuaime", + "NotificationOptionAudioPlaybackStopped": "Cuireadh deireadh le hathsheinm fuaime", + "NotificationOptionCameraImageUploaded": "Íosluchtaigh grianghraf ceamara", + "NotificationOptionInstallationFailed": "Teip suiteála", + "NotificationOptionNewLibraryContent": "Ábhar nua curtha leis", + "NotificationOptionPluginError": "Teip breiseán", + "NotificationOptionPluginInstalled": "Breiseán suiteáilte", + "NotificationOptionPluginUninstalled": "Breiseán díshuiteáilte", + "NotificationOptionPluginUpdateInstalled": "Nuashonrú breiseán suiteáilte", + "NotificationOptionServerRestartRequired": "Teastaíonn atosú an fhreastalaí", + "NotificationOptionTaskFailed": "Teip tasc sceidealta", + "NotificationOptionUserLockedOut": "Úsáideoir glasáilte amach", + "NotificationOptionVideoPlayback": "Cuireadh tús le hathsheinm físe", + "NotificationOptionVideoPlaybackStopped": "Cuireadh deireadh le hathsheinm físe", + "Photos": "Grianghraif", + "Playlists": "Seinmliostaí", + "Plugin": "Breiseán", + "PluginInstalledWithName": "Suiteáladh {0}", + "PluginUninstalledWithName": "Díshuiteáladh {0}", + "PluginUpdatedWithName": "Nuashonraíodh {0}", + "ProviderValue": "Soláthraí: {0}", + "ScheduledTaskFailedWithName": "Theip ar {0}", + "ScheduledTaskStartedWithName": "Thosaigh {0}", + "ServerNameNeedsToBeRestarted": "Ní mór {0} a atosú", + "Shows": "Seónna", + "Songs": "Amhráin", + "StartupEmbyServerIsLoading": "Tá freastalaí Jellyfin á luchtú. Bain triail eile as gan mhoill.", + "SubtitleDownloadFailureFromForItem": "Theip ar fhotheidil a íoslódáil ó {0} le haghaidh {1}", + "Sync": "Sioncrónaigh", + "System": "Córas", + "TvShows": "Seónna Teilifíse", + "Undefined": "Neamhshainithe", + "User": "Úsáideoir", + "UserCreatedWithName": "Cruthaíodh úsáideoir {0}", + "UserDeletedWithName": "Scriosadh úsáideoir {0}", + "UserDownloadingItemWithValues": "Tá {0} á íoslódáil {1}", + "UserLockedOutWithName": "Tá úsáideoir {0} glasáilte amach", + "UserOfflineFromDevice": "Tá {0} dícheangailte ó {1}", + "UserOnlineFromDevice": "Tá {0} ar líne ó {1}", + "UserPasswordChangedWithName": "Athraíodh pasfhocal don úsáideoir {0}", + "UserPolicyUpdatedWithName": "Nuashonraíodh polasaí úsáideora le haghaidh {0}", + "UserStartedPlayingItemWithValues": "Tá {0} ag seinnt {1} ar {2}", + "UserStoppedPlayingItemWithValues": "Chríochnaigh {0} ag imirt {1} ar {2}", + "ValueHasBeenAddedToLibrary": "Cuireadh {0} le do leabharlann meán", + "ValueSpecialEpisodeName": "Speisialta - {0}", + "VersionNumber": "Leagan {0}", + "TasksMaintenanceCategory": "Cothabháil", + "TasksLibraryCategory": "Leabharlann", + "TasksApplicationCategory": "Feidhmchlár", + "TasksChannelsCategory": "Cainéil Idirlín", + "TaskCleanActivityLog": "Loga Gníomhaíochta Glan", + "TaskCleanActivityLogDescription": "Scrios iontrálacha loga gníomhaíochta atá níos sine ná an aois chumraithe.", + "TaskCleanCache": "Eolaire Taisce Glan", + "TaskCleanCacheDescription": "Scriostar comhaid taisce nach bhfuil ag teastáil ón gcóras a thuilleadh.", + "TaskRefreshChapterImages": "Sliocht Íomhánna Caibidil", + "TaskRefreshChapterImagesDescription": "Cruthaíonn mionsamhlacha le haghaidh físeáin a bhfuil caibidlí acu.", + "TaskRefreshLibrary": "Scan Leabharlann na Meán", + "TaskRefreshPeople": "Daoine Athnuaigh", + "TaskUpdatePlugins": "Nuashonraigh Breiseáin", + "TaskCleanTranscodeDescription": "Scriostar comhaid traschódaithe níos mó ná lá amháin d'aois.", + "TaskCleanTranscode": "Eolaire Transcode Glan", + "TaskDownloadMissingSubtitles": "Íosluchtaigh fotheidil ar iarraidh", + "CleanupUserDataTask": "Tasc glantacháin sonraí úsáideora", + "CleanupUserDataTaskDescription": "Glanann sé gach sonraí úsáideora (stádas faire, stádas is fearr leat srl.) ó mheáin nach bhfuil i láthair a thuilleadh ar feadh 90 lá ar a laghad." +} diff --git a/Emby.Server.Implementations/Localization/Core/gl.json b/Emby.Server.Implementations/Localization/Core/gl.json new file mode 100644 index 00000000..b3f137fe --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/gl.json @@ -0,0 +1,141 @@ +{ + "Albums": "Álbums", + "Collections": "Coleccións", + "ChapterNameValue": "Capítulo {0}", + "Channels": "Canles", + "CameraImageUploadedFrom": "Cargouse unha nova imaxe de cámara dende {0}", + "Books": "Libros", + "AuthenticationSucceededWithUserName": "{0} autenticouse correctamente", + "Artists": "Artistas", + "Application": "Aplicación", + "NotificationOptionServerRestartRequired": "Necesario o reinicio do servidor", + "NotificationOptionPluginUpdateInstalled": "Actualización do plugin instalada", + "NotificationOptionPluginUninstalled": "Plugin desinstalado", + "NotificationOptionPluginInstalled": "Plugin instalado", + "NotificationOptionPluginError": "Fallo do plugin", + "NotificationOptionNewLibraryContent": "Novo contido engadido", + "NotificationOptionInstallationFailed": "Fallo na instalación", + "NotificationOptionCameraImageUploaded": "Imaxe da cámara cargada", + "NotificationOptionAudioPlaybackStopped": "Reproducción de audio detida", + "NotificationOptionAudioPlayback": "Reproducción de audio comezada", + "NotificationOptionApplicationUpdateInstalled": "Actualización da aplicación instalada", + "NotificationOptionApplicationUpdateAvailable": "Actualización da aplicación dispoñible", + "NewVersionIsAvailable": "Nova versión do Servidor Jellyfin dispoñible para descargar.", + "NameSeasonUnknown": "Tempada descoñecida", + "NameSeasonNumber": "Tempada {0}", + "NameInstallFailed": "{0} instalación fallida", + "MusicVideos": "Vídeos musicais", + "Music": "Música", + "Movies": "Películas", + "MixedContent": "Contido mixto", + "MessageServerConfigurationUpdated": "Actualizouse a configuración do servidor", + "MessageNamedServerConfigurationUpdatedWithValue": "Actualizouse a sección de configuración {0} do servidor", + "MessageApplicationUpdatedTo": "O servidor Jellyfin actualizouse a {0}", + "MessageApplicationUpdated": "O servidor Jellyfin actualizouse", + "Latest": "Último", + "LabelRunningTimeValue": "Tempo en execución: {0}", + "LabelIpAddressValue": "Enderezo IP: {0}", + "ItemRemovedWithName": "{0} eliminouse da biblioteca", + "ItemAddedWithName": "{0} engadiuse á biblioteca", + "Inherit": "Herdar", + "HomeVideos": "Videos caseiros", + "HeaderRecordingGroups": "Grupos de grabación", + "HeaderNextUp": "De seguido", + "HeaderLiveTV": "TV en directo", + "HeaderFavoriteSongs": "Cancións favoritas", + "HeaderFavoriteShows": "Series de TV favoritas", + "HeaderFavoriteEpisodes": "Episodios favoritos", + "HeaderFavoriteArtists": "Artistas favoritos", + "HeaderFavoriteAlbums": "Álbums favoritos", + "HeaderContinueWatching": "Seguir vendo", + "HeaderAlbumArtists": "Artistas do álbum", + "Genres": "Xéneros", + "Forced": "Forzado", + "Folders": "Cartafoles", + "Favorites": "Favoritos", + "FailedLoginAttemptWithUserName": "Fallo de intento de inicio de sesión dende {0}", + "DeviceOnlineWithName": "{0} conectouse", + "DeviceOfflineWithName": "{0} desconectouse", + "Default": "Por defecto", + "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}", + "TaskCleanLogs": "Limpar directorio de rexistros", + "TaskCleanActivityLog": "Limpar rexistro de actividade", + "TasksChannelsCategory": "Canles da Internet", + "TaskUpdatePlugins": "Actualizar plugins", + "User": "Usuario", + "Undefined": "Sen definir", + "TvShows": "Programas de TV", + "System": "Sistema", + "Sync": "Sincronizar", + "SubtitleDownloadFailureFromForItem": "Fallou a descarga de subtítulos para {1} dende {0}", + "StartupEmbyServerIsLoading": "O servidor Jellyfin está cargando. Por favor, ténteo axiña outra vez.", + "Songs": "Cancións", + "Shows": "Programas", + "ServerNameNeedsToBeRestarted": "{0} precisa ser reiniciado", + "ScheduledTaskStartedWithName": "{0} comezou", + "ScheduledTaskFailedWithName": "{0} fallou", + "ProviderValue": "Provedor: {0}", + "PluginUpdatedWithName": "{0} foi actualizado", + "PluginUninstalledWithName": "{0} foi desinstalado", + "PluginInstalledWithName": "{0} foi instalado", + "Playlists": "Listas de reproducción", + "Photos": "Fotos", + "UserLockedOutWithName": "O usuario {0} foi bloqueado", + "UserDownloadingItemWithValues": "{0} está a ser transferido {1}", + "UserDeletedWithName": "O usuario {0} foi borrado", + "UserCreatedWithName": "O usuario {0} foi creado", + "Plugin": "Plugin", + "NotificationOptionVideoPlaybackStopped": "Reproducción de vídeo detida", + "NotificationOptionVideoPlayback": "Reproducción de vídeo iniciada", + "NotificationOptionUserLockedOut": "Usuario bloqueado", + "NotificationOptionTaskFailed": "Falla na tarefa axendada", + "TaskCleanTranscodeDescription": "Borra os ficheiros de transcodificación de hai más dun día.", + "TaskCleanTranscode": "Limpar o directorio de transcodificación", + "UserStoppedPlayingItemWithValues": "{0} rematou de reproducir {1} en {2}", + "UserStartedPlayingItemWithValues": "{0} está a reproducir {1} en {2}", + "TaskDownloadMissingSubtitlesDescription": "Procura na internet os subtítulos que faltan segundo a configuración de metadatos.", + "TaskDownloadMissingSubtitles": "Descargar subtítulos que faltan", + "TaskRefreshChannelsDescription": "Refresca a información da canle de internet.", + "TaskRefreshChannels": "Refrescar canles", + "TaskUpdatePluginsDescription": "Descarga e instala actualizacións dos plugins configurados para actualizarse automáticamente.", + "TaskRefreshPeopleDescription": "Actualiza os metadatos dos actores e directores na túa biblioteca de medios.", + "TaskRefreshPeople": "Refrescar persoas", + "TaskCleanLogsDescription": "Borra ficheiros de rexistro con máis de {0} días de antigüidade.", + "TaskRefreshLibraryDescription": "Escanea a túa biblioteca de medios á procura de novos ficheiros e refresca os metadatos.", + "TaskRefreshLibrary": "Escanear a biblioteca de medios", + "TaskRefreshChapterImagesDescription": "Crea miniaturas dos vídeos que teñen capítulos.", + "TaskRefreshChapterImages": "Extraer imaxes dos capítulos", + "TaskCleanCacheDescription": "Borra ficheiros da caché que xa non son necesarios para o sistema.", + "TaskCleanCache": "Limpar directorio de caché", + "TaskCleanActivityLogDescription": "Borra do rexistro de actividade as entradas anteriores á data configurada.", + "TasksApplicationCategory": "Aplicación", + "ValueSpecialEpisodeName": "Especial - {0}", + "ValueHasBeenAddedToLibrary": "{0} engadiuse á túa biblioteca de medios", + "TasksLibraryCategory": "Biblioteca", + "TasksMaintenanceCategory": "Mantemento", + "VersionNumber": "Versión {0}", + "UserPolicyUpdatedWithName": "A política de usuario foi actualizada para {0}", + "UserPasswordChangedWithName": "Cambiouse o contrasinal para o usuario {0}", + "UserOnlineFromDevice": "{0} está en liña desde {1}", + "UserOfflineFromDevice": "{0} desconectouse dende {1}", + "TaskOptimizeDatabaseDescription": "Compacta e libera espazo na base de datos. Executar esta tarefa logo de facer cambios que muden a base de datos ou despois de escanear a biblioteca pode mellorar o rendemento.", + "TaskOptimizeDatabase": "Optimizar base de datos", + "TaskKeyframeExtractorDescription": "Extrae fotogramas clave dos vídeos para crear listas de reprodución HLS máis precisas. Podería levar moito tempo.", + "External": "Externo", + "HearingImpaired": "Problemas de audición", + "TaskKeyframeExtractor": "Extractor de fotogramas clave", + "TaskAudioNormalization": "Normalización de volume", + "TaskRefreshTrickplayImagesDescription": "Crea miniaturas de previsualización para os vídeos nas bibliotecas habilitadas.", + "TaskDownloadMissingLyrics": "Descargar letras que faltan", + "TaskDownloadMissingLyricsDescription": "Descarga as letras das cancións", + "TaskCleanCollectionsAndPlaylists": "Limpar coleccións e listas de reprodución", + "TaskCleanCollectionsAndPlaylistsDescription": "Quita ítems que xa non existen das coleccións e listas de reprodución.", + "TaskExtractMediaSegmentsDescription": "Procura segmentos de medios cos plugins habilitados.", + "TaskExtractMediaSegments": "Escaneo de segmentos de medios", + "TaskMoveTrickplayImages": "Migrar as miniaturas de previsualización a outra ubicación", + "TaskMoveTrickplayImagesDescription": "Move as miniaturas de previsualización segundo a configuración da biblioteca.", + "TaskRefreshTrickplayImages": "Xerar miniaturas de previsualización", + "TaskAudioNormalizationDescription": "Escanea ficheiros á procura de datos de normalización de volume.", + "CleanupUserDataTask": "Tarefa de limpeza de datos dos usuarios", + "CleanupUserDataTaskDescription": "Limpa todos os datos do usuario (estado de visualización, de favorito etc.) dos medios ausentes polo menos 90 días." +} diff --git a/Emby.Server.Implementations/Localization/Core/gsw.json b/Emby.Server.Implementations/Localization/Core/gsw.json new file mode 100644 index 00000000..9be6f05e --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/gsw.json @@ -0,0 +1,129 @@ +{ + "Albums": "Alben", + "AppDeviceValues": "App: {0}, Gerät: {1}", + "Application": "Applikation", + "Artists": "Künstler", + "AuthenticationSucceededWithUserName": "{0} hat sich angemeldet", + "Books": "Bücher", + "CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen", + "Channels": "Kanäle", + "ChapterNameValue": "Kapitel {0}", + "Collections": "Sammlungen", + "DeviceOfflineWithName": "{0} wurde getrennt", + "DeviceOnlineWithName": "{0} ist verbunden", + "FailedLoginAttemptWithUserName": "Fählgschlagene Ameldeversuech vo {0}", + "Favorites": "Favorite", + "Folders": "Ordner", + "Genres": "Genre", + "HeaderAlbumArtists": "Album-Künschtler", + "HeaderContinueWatching": "weiter schauen", + "HeaderFavoriteAlbums": "Lieblingsalben", + "HeaderFavoriteArtists": "Lieblings-Künstler", + "HeaderFavoriteEpisodes": "Lieblingsepisoden", + "HeaderFavoriteShows": "Lieblingsserien", + "HeaderFavoriteSongs": "Lieblingslieder", + "HeaderLiveTV": "Live-Fernseh", + "HeaderNextUp": "Als Nächstes", + "HeaderRecordingGroups": "Aufnahme-Gruppen", + "HomeVideos": "Heimvideos", + "Inherit": "Vererben", + "ItemAddedWithName": "{0} wurde der Bibliothek hinzugefügt", + "ItemRemovedWithName": "{0} wurde aus der Bibliothek entfernt", + "LabelIpAddressValue": "IP-Adresse: {0}", + "LabelRunningTimeValue": "Laufzeit: {0}", + "Latest": "Neueste", + "MessageApplicationUpdated": "Jellyfin-Server wurde aktualisiert", + "MessageApplicationUpdatedTo": "Jellyfin-Server wurde auf Version {0} aktualisiert", + "MessageNamedServerConfigurationUpdatedWithValue": "Der Server-Einstellungsbereich {0} wurde aktualisiert", + "MessageServerConfigurationUpdated": "Serveriistöuige send aktualisiert worde", + "MixedContent": "Gmeschti Inhäut", + "Movies": "Film", + "Music": "Musig", + "MusicVideos": "Musigvideos", + "NameInstallFailed": "Installation vo {0} fäugschlage", + "NameSeasonNumber": "Staffle {0}", + "NameSeasonUnknown": "Staffle unbekannt", + "NewVersionIsAvailable": "E nöi Version vo Jellyfin Server esch zom Download parat.", + "NotificationOptionApplicationUpdateAvailable": "Aawändigsupdate verfüegbar", + "NotificationOptionApplicationUpdateInstalled": "Aawändigsupdate installiert", + "NotificationOptionAudioPlayback": "Audiowedergab gstartet", + "NotificationOptionAudioPlaybackStopped": "Audiwedergab gstoppt", + "NotificationOptionCameraImageUploaded": "Foti ueglade", + "NotificationOptionInstallationFailed": "Installationsfähler", + "NotificationOptionNewLibraryContent": "Nöie Inhaut hinzuegfüegt", + "NotificationOptionPluginError": "Plugin-Fäuer", + "NotificationOptionPluginInstalled": "Plugin installiert", + "NotificationOptionPluginUninstalled": "Plugin deinstalliert", + "NotificationOptionPluginUpdateInstalled": "Pluginupdate installiert", + "NotificationOptionServerRestartRequired": "Serverneustart notwändig", + "NotificationOptionTaskFailed": "Planti Uufgab fäugschlage", + "NotificationOptionUserLockedOut": "Benotzer usgschlosse", + "NotificationOptionVideoPlayback": "Videowedergab gstartet", + "NotificationOptionVideoPlaybackStopped": "Videowedergab gstoppt", + "Photos": "Fotis", + "Playlists": "Wedergabeliste", + "Plugin": "Plugin", + "PluginInstalledWithName": "{0} esch installiert worde", + "PluginUninstalledWithName": "{0} esch deinstalliert worde", + "PluginUpdatedWithName": "{0} esch updated worde", + "ProviderValue": "Aabieter: {0}", + "ScheduledTaskFailedWithName": "{0} esch fäugschlage", + "ScheduledTaskStartedWithName": "{0} het gstartet", + "ServerNameNeedsToBeRestarted": "{0} mues nöi gstartet wärde", + "Shows": "Serie", + "Songs": "Lieder", + "StartupEmbyServerIsLoading": "Jellyfin Server ladt. Bitte grad noeinisch probiere.", + "SubtitleDownloadFailureFromForItem": "Ondertetle vo {0} för {1} hend ned chönne abeglade wärde", + "Sync": "Synchronisation", + "System": "System", + "TvShows": "Färnsehserie", + "User": "Benotzer", + "UserCreatedWithName": "Benotzer {0} esch erstöut worde", + "UserDeletedWithName": "Benotzer {0} esch glösche worde", + "UserDownloadingItemWithValues": "{0} ladt {1} abe", + "UserLockedOutWithName": "Benotzer {0} esch usgschlosse worde", + "UserOfflineFromDevice": "{0} esch vo {1} trennt worde", + "UserOnlineFromDevice": "{0} esch online vo {1}", + "UserPasswordChangedWithName": "S'Passwort för Benotzer {0} esch gänderet worde", + "UserPolicyUpdatedWithName": "Benotzerrechtlinie för {0} esch aktualisiert worde", + "UserStartedPlayingItemWithValues": "{0} hed d'Wedergab vo {1} of {2} gstartet", + "UserStoppedPlayingItemWithValues": "{0} het d'Wedergab vo {1} of {2} gstoppt", + "ValueHasBeenAddedToLibrary": "{0} esch dinnere Biblithek hinzuegfüegt worde", + "ValueSpecialEpisodeName": "Extra - {0}", + "VersionNumber": "Version {0}", + "TaskCleanLogs": "Lösche Log Pfad", + "TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.", + "TaskRefreshLibrary": "Scanne alle Bibliotheken", + "TaskRefreshChapterImagesDescription": "Kreiert Vorschaubilder für Videos welche Kapitel haben.", + "TaskRefreshChapterImages": "Extrahiere Kapitel-Bilder", + "TaskCleanCacheDescription": "Löscht Zwischenspeicherdatein die nicht länger von System gebraucht werden.", + "TaskCleanCache": "Leere Cache Pfad", + "TasksChannelsCategory": "Internet Kanäle", + "TasksApplicationCategory": "Applikation", + "TasksLibraryCategory": "Bibliothek", + "TasksMaintenanceCategory": "Verwaltung", + "TaskDownloadMissingSubtitlesDescription": "Durchsucht das Internet nach fehlenden Untertiteln, basierend auf den Metadaten Einstellungen.", + "TaskDownloadMissingSubtitles": "Lade fehlende Untertitel herunter", + "TaskRefreshChannelsDescription": "Aktualisiert Internet Kanal Informationen.", + "TaskRefreshChannels": "Aktualisiere Kanäle", + "TaskCleanTranscodeDescription": "Löscht Transkodierdateien welche älter als ein Tag sind.", + "TaskCleanTranscode": "Räume Transcodier Verzeichnis auf", + "TaskUpdatePluginsDescription": "Lädt Aktualisierungen für Erweiterungen herunter und installiert diese, für welche automatische Aktualisierungen konfiguriert sind.", + "TaskUpdatePlugins": "Aktualisiere Erweiterungen", + "TaskRefreshPeopleDescription": "Aktualisiert Metadaten für Schausteller und Regisseure in deiner Bibliothek.", + "TaskRefreshPeople": "Aktualisiere Schauspieler", + "TaskCleanLogsDescription": "Löscht Log Dateien die älter als {0} Tage sind.", + "TaskCleanActivityLogDescription": "Löscht Aktivitätsprotokolleinträge, die älter als das konfigurierte Alter sind.", + "TaskCleanActivityLog": "Aktivitätsprotokoll aufräumen", + "Undefined": "Undefiniert", + "Forced": "Erzwungen", + "Default": "Standard", + "TaskOptimizeDatabase": "Datenbank optimieren", + "External": "Extern", + "TaskOptimizeDatabaseDescription": "Kompromiert d Datenbank und trennt freie Speicherplatz. Durch die Ufagb cha d Leistig nach em ne Scan vor Bibliothek oder andere Ufgabe verbesseret werde.", + "HearingImpaired": "Hörgschädigti", + "TaskKeyframeExtractor": "Keyframe-Extraktor", + "TaskKeyframeExtractorDescription": "Extrahiert Keyframes us Videodateien zum erstelle vo genauere HLS Playliste. Die Ufgab cha für e langi Zyt laufe.", + "TaskRefreshTrickplayImages": "Trickplay-Bilder erstelle", + "TaskRefreshTrickplayImagesDescription": "Erstellt Trickplay-Vorschaue für Video in aktivierte Bibliothèke." +} diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json new file mode 100644 index 00000000..ef95a639 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/he.json @@ -0,0 +1,141 @@ +{ + "Albums": "אלבומים", + "AppDeviceValues": "יישום: {0}, מכשיר: {1}", + "Application": "יישום", + "Artists": "אומנים", + "AuthenticationSucceededWithUserName": "{0} אומת בהצלחה", + "Books": "ספרים", + "CameraImageUploadedFrom": "תמונת מצלמה חדשה הועלתה מתוך {0}", + "Channels": "ערוצים", + "ChapterNameValue": "פרק {0}", + "Collections": "אוספים", + "DeviceOfflineWithName": "{0} התנתק", + "DeviceOnlineWithName": "{0} מחובר", + "FailedLoginAttemptWithUserName": "ניסיון כניסה שגוי דרך {0}", + "Favorites": "מועדפים", + "Folders": "תיקיות", + "Genres": "ז׳אנרים", + "HeaderAlbumArtists": "אמני האלבום", + "HeaderContinueWatching": "המשך צפייה", + "HeaderFavoriteAlbums": "אלבומים מועדפים", + "HeaderFavoriteArtists": "אמנים מועדפים", + "HeaderFavoriteEpisodes": "פרקים מועדפים", + "HeaderFavoriteShows": "תוכניות מועדפות", + "HeaderFavoriteSongs": "שירים מועדפים", + "HeaderLiveTV": "שידורים חיים", + "HeaderNextUp": "הבא בתור", + "HeaderRecordingGroups": "קבוצות הקלטה", + "HomeVideos": "סרטונים בייתים", + "Inherit": "הורש", + "ItemAddedWithName": "{0} נוסף לספרייה", + "ItemRemovedWithName": "{0} נמחק מהספרייה", + "LabelIpAddressValue": "Ip כתובת: {0}", + "LabelRunningTimeValue": "משך צפייה: {0}", + "Latest": "אחרון", + "MessageApplicationUpdated": "שרת Jellyfin עודכן", + "MessageApplicationUpdatedTo": "שרת Jellyfin עודכן לגרסה {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "סעיף הגדרת השרת {0} עודכן", + "MessageServerConfigurationUpdated": "תצורת השרת עודכנה", + "MixedContent": "תוכן מעורב", + "Movies": "סרטים", + "Music": "מוזיקה", + "MusicVideos": "קליפים", + "NameInstallFailed": "התקנת {0} נכשלה", + "NameSeasonNumber": "עונה {0}", + "NameSeasonUnknown": "עונה לא ידועה", + "NewVersionIsAvailable": "גרסה חדשה של שרת Jellyfin זמינה להורדה.", + "NotificationOptionApplicationUpdateAvailable": "קיים עדכון זמין ליישום", + "NotificationOptionApplicationUpdateInstalled": "עדכון ליישום הותקן", + "NotificationOptionAudioPlayback": "ניגון שמע החל", + "NotificationOptionAudioPlaybackStopped": "ניגון שמע הופסק", + "NotificationOptionCameraImageUploaded": "תמונת מצלמה הועלתה", + "NotificationOptionInstallationFailed": "התקנה נכשלה", + "NotificationOptionNewLibraryContent": "תוכן חדש נוסף", + "NotificationOptionPluginError": "כשלון בתוסף", + "NotificationOptionPluginInstalled": "התוסף הותקן", + "NotificationOptionPluginUninstalled": "התוסף הוסר", + "NotificationOptionPluginUpdateInstalled": "העדכון לתוסף הותקן", + "NotificationOptionServerRestartRequired": "יש לאתחל את השרת", + "NotificationOptionTaskFailed": "משימה מתוזמנת נכשלה", + "NotificationOptionUserLockedOut": "משתמש ננעל", + "NotificationOptionVideoPlayback": "ניגון וידאו החל", + "NotificationOptionVideoPlaybackStopped": "ניגון וידאו הופסק", + "Photos": "צילומים", + "Playlists": "רשימות נגינה", + "Plugin": "תוסף", + "PluginInstalledWithName": "{0} הותקן", + "PluginUninstalledWithName": "{0} הוסר", + "PluginUpdatedWithName": "{0} עודכן", + "ProviderValue": "ספק: {0}", + "ScheduledTaskFailedWithName": "{0} נכשל", + "ScheduledTaskStartedWithName": "{0} החל", + "ServerNameNeedsToBeRestarted": "{0} דורש הפעלה מחדש", + "Shows": "סדרות", + "Songs": "שירים", + "StartupEmbyServerIsLoading": "שרת Jellyfin בתהליך טעינה. נא לנסות שוב בקרוב.", + "SubtitleDownloadFailureFromForItem": "הורדת כתוביות מ־{0} עבור {1} נכשלה", + "Sync": "סנכרון", + "System": "מערכת", + "TvShows": "סדרות טלוויזיה", + "User": "משתמש", + "UserCreatedWithName": "המשתמש {0} נוצר", + "UserDeletedWithName": "המשתמש {0} הוסר", + "UserDownloadingItemWithValues": "{0} מוריד את {1}", + "UserLockedOutWithName": "המשתמש {0} ננעל", + "UserOfflineFromDevice": "{0} התנתק מ־{1}", + "UserOnlineFromDevice": "{0} מחובר מ־{1}", + "UserPasswordChangedWithName": "הסיסמה שונתה עבור המשתמש {0}", + "UserPolicyUpdatedWithName": "מדיניות המשתמש {0} עודכנה", + "UserStartedPlayingItemWithValues": "{0} מנגן את {1} על {2}", + "UserStoppedPlayingItemWithValues": "{0} סיים לנגן את {1} על {2}", + "ValueHasBeenAddedToLibrary": "{0} התווסף לספריית המדיה שלך", + "ValueSpecialEpisodeName": "מיוחד- {0}", + "VersionNumber": "גרסה {0}", + "TaskRefreshLibrary": "סרוק ספריית מדיה", + "TaskRefreshChapterImages": "חלץ תמונות פרקים", + "TaskCleanCacheDescription": "מחק קבצי מטמון שלא בשימוש המערכת.", + "TaskCleanCache": "ניקוי תיקיית מטמון", + "TasksApplicationCategory": "יישום", + "TasksLibraryCategory": "ספרייה", + "TasksMaintenanceCategory": "תחזוקה", + "TaskUpdatePlugins": "עדכן תוספים", + "TaskRefreshPeopleDescription": "מעדכן מטא-דאטה עבור שחקנים ובמאים בספריית המדיה שלך.", + "TaskRefreshPeople": "רענן אנשים", + "TaskCleanLogsDescription": "מוחק קבצי יומן בני יותר מ- {0} ימים.", + "TaskCleanLogs": "ניקוי תיקיית יומן", + "TaskRefreshLibraryDescription": "סורק את ספריית המדיה שלך אחר קבצים חדשים ומרענן מטא-דאטה.", + "TaskRefreshChapterImagesDescription": "יוצר תמונות ממוזערות לסרטונים שיש להם פרקים.", + "TasksChannelsCategory": "ערוצי אינטרנט", + "TaskDownloadMissingSubtitlesDescription": "חפש באינטרנט כתוביות חסרות בהתבסס על המטא-דאטה.", + "TaskDownloadMissingSubtitles": "הורד כתוביות חסרות", + "TaskRefreshChannelsDescription": "רענן פרטי ערוץ אינטרנטי.", + "TaskRefreshChannels": "רענן ערוץ", + "TaskCleanTranscodeDescription": "מחק קבצי transcode שנוצרו מלפני יותר מיום.", + "TaskCleanTranscode": "נקה תקיית Transcode", + "TaskUpdatePluginsDescription": "הורד והתקן עדכונים עבור תוספים שמוגדרים לעדכון אוטומטי.", + "TaskCleanActivityLogDescription": "מחק רשומת פעילות הישנה יותר מהגיל המוגדר.", + "TaskCleanActivityLog": "נקה רשומת פעילות", + "Undefined": "לא מוגדר", + "Forced": "כפוי", + "Default": "ברירת מחדל", + "TaskOptimizeDatabase": "מיטוב מסד נתונים", + "TaskOptimizeDatabaseDescription": "דוחס את מסד הנתונים ומוריד את שטח האחסון שבשימוש. הרצה של פעולה זו לאחר סריקת הספרייה או שינויים אחרים שמשפיעים על מסד הנתונים יכולה לשפר ביצועים.", + "TaskKeyframeExtractorDescription": "חלץ תמונות מפתח מקבצי וידאו בכדי ליצור רשימות השמעה מדויקות יותר של HLS. משימה זו עלולה להימשך זמן רב.", + "TaskKeyframeExtractor": "מחלץ תמונות מפתח", + "External": "חיצוני", + "HearingImpaired": "לקוי שמיעה", + "TaskRefreshTrickplayImages": "יצירת תמונות Trickplay", + "TaskRefreshTrickplayImagesDescription": "יוצר תמונות Trickplay לסרטונים בספריות הפעילות.", + "TaskAudioNormalization": "נרמול שמע", + "TaskCleanCollectionsAndPlaylistsDescription": "מנקה פריטים לא קיימים מאוספים ורשימות השמעה.", + "TaskAudioNormalizationDescription": "מחפש קבצי נורמליזציה של שמע.", + "TaskCleanCollectionsAndPlaylists": "מנקה אוספים ורשימות השמעה", + "TaskDownloadMissingLyrics": "הורדת מילים חסרות", + "TaskDownloadMissingLyricsDescription": "הורדת מילים לשירים", + "TaskMoveTrickplayImages": "העברת מיקום של תמונות Trickplay", + "TaskExtractMediaSegments": "סריקת מדיה", + "TaskExtractMediaSegmentsDescription": "מחלץ חלקי מדיה מתוספים המאפשרים זאת.", + "TaskMoveTrickplayImagesDescription": "הזזת קבצי Trickplay קיימים בהתאם להגדרות הספרייה.", + "CleanupUserDataTaskDescription": "ניקוי כל המידע של המשתמש (מצב צפייה, מועדפים וכו) ממדיה שאינה קיימת מעל 90 יום.", + "CleanupUserDataTask": "משימת ניקוי מידע משתמש" +} diff --git a/Emby.Server.Implementations/Localization/Core/he_IL.json b/Emby.Server.Implementations/Localization/Core/he_IL.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/he_IL.json @@ -0,0 +1 @@ +{} diff --git a/Emby.Server.Implementations/Localization/Core/hi.json b/Emby.Server.Implementations/Localization/Core/hi.json new file mode 100644 index 00000000..80db975c --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/hi.json @@ -0,0 +1,140 @@ +{ + "Albums": "एल्बम", + "HeaderRecordingGroups": "रिकॉर्डिंग समूह", + "HeaderNextUp": "इसके बाद", + "HeaderLiveTV": "लाइव टीवी", + "HeaderFavoriteSongs": "पसंदीदा गीत", + "HeaderFavoriteShows": "पसंदीदा शो", + "HeaderFavoriteEpisodes": "पसंदीदा प्रकरण", + "HeaderFavoriteArtists": "पसंदीदा कलाकार", + "HeaderFavoriteAlbums": "पसंदीदा एलबम्स", + "HeaderContinueWatching": "देखना जारी रखें", + "HeaderAlbumArtists": "एल्बम कलाकार", + "Genres": "शैलियां", + "Forced": "बलपूर्वक", + "Folders": "फ़ोल्डर", + "Favorites": "पसंदीदा", + "FailedLoginAttemptWithUserName": "{0} से संप्रवेश असफल हुआ", + "DeviceOnlineWithName": "{0} कनेक्ट हो गया है", + "DeviceOfflineWithName": "{0} डिस्कनेक्ट हो गया है", + "Default": "प्राथमिक", + "Collections": "संग्रह", + "ChapterNameValue": "अध्याय {0}", + "Channels": "चैनल", + "CameraImageUploadedFrom": "{0} से एक नया कैमरा छवि अपलोड की गई है", + "Books": "पुस्तकें", + "AuthenticationSucceededWithUserName": "{0} सफलतापूर्वक प्रमाणित किया गया", + "Artists": "कलाकार", + "Application": "एप्लिकेशन", + "AppDeviceValues": "एप: {0}, उपकरण: {1}", + "NotificationOptionPluginUninstalled": "प्लगइन अनइंस्टाल हो गया", + "NotificationOptionPluginInstalled": "प्लगइन इनस्टॉल हो गया", + "NotificationOptionPluginError": "प्लगइन फ़ैल हो गया", + "NotificationOptionInstallationFailed": "इंस्टालेशन फ़ैल हो गया", + "NotificationOptionAudioPlaybackStopped": "संगीत बंद कर दिया गया", + "NotificationOptionAudioPlayback": "संगीत शुरू कर दिया गया", + "NotificationOptionCameraImageUploaded": "कैमरा फोटो अपलोड किया गया", + "NotificationOptionApplicationUpdateInstalled": "एप्लीकेशन अपडेट इनस्टॉल कर दिया है", + "NotificationOptionApplicationUpdateAvailable": "एप्लीकेशन अपडेट उपलभ्द है", + "NewVersionIsAvailable": "जेलीफिन सर्वर का एक नया वर्जन डाउनलोड के लिए उपलब्ध है।", + "NameSeasonUnknown": "अनजान भाग", + "NameSeasonNumber": "भाग {0}", + "NameInstallFailed": "{0} इनस्टॉल करते समय फेल हो गया है", + "MusicVideos": "संगीत वीडियो", + "Music": "संगीत", + "Movies": "फ़िल्म", + "MixedContent": "मिला-जुला कंटेंट", + "MessageServerConfigurationUpdated": "सर्वर कॉन्फ़िगरेशन अपडेट हो गया है", + "MessageNamedServerConfigurationUpdatedWithValue": "सर्वर कॉन्फ़िगरेशन भाग {0} अपडेट हो गया है", + "MessageApplicationUpdatedTo": "जैलीफिन सर्वर {0} में अपडेट हो गया है", + "MessageApplicationUpdated": "जैलीफिन सर्वर अपडेट हो गया है", + "Latest": "सबसे नया", + "LabelIpAddressValue": "आई पी एड्रेस: {0}", + "ItemRemovedWithName": "{0} लाइब्रेरी में से निकाल दिया है", + "HomeVideos": "होम चलचित्र", + "NotificationOptionVideoPlayback": "वीडियो प्लेबैक शुरू हुआ", + "NotificationOptionUserLockedOut": "उपयोगकर्ता लॉक हो गया", + "NotificationOptionTaskFailed": "निर्धारित कार्य विफलता", + "NotificationOptionServerRestartRequired": "सर्वर पुनरारंभ आवश्यक है", + "NotificationOptionPluginUpdateInstalled": "प्लगइन अद्यतन स्थापित", + "NotificationOptionNewLibraryContent": "नई सामग्री जोड़ी गई", + "LabelRunningTimeValue": "चलने का समय: {0}", + "ItemAddedWithName": "{0} को लाइब्रेरी में जोड़ा गया", + "Inherit": "इनहेरिट", + "NotificationOptionVideoPlaybackStopped": "चलचित्र रुका हुआ", + "PluginUninstalledWithName": "{0} अनइंस्टॉल हुए", + "PluginInstalledWithName": "{0} इंस्टॉल हुए", + "Plugin": "प्लग-इन", + "Playlists": "प्लेलिस्ट", + "Photos": "तस्वीरें", + "External": "बाहरी", + "PluginUpdatedWithName": "{0} अपडेट हुए", + "ScheduledTaskStartedWithName": "{0} शुरू हुए", + "Songs": "गाने", + "UserStartedPlayingItemWithValues": "{0} {2} पर {1} खेल रहे हैं", + "UserStoppedPlayingItemWithValues": "{0} ने {2} पर {1} खेलना खत्म किया", + "StartupEmbyServerIsLoading": "जेलीफ़िन सर्वर लोड हो रहा है। कृपया शीघ्र ही पुन: प्रयास करें।", + "ServerNameNeedsToBeRestarted": "{0} रीस्टार्ट करने की आवश्यकता है", + "UserCreatedWithName": "उपयोगकर्ता {0} बनाया गया", + "UserDownloadingItemWithValues": "{0} डाउनलोड हो रहा है", + "UserOfflineFromDevice": "{0} {1} से डिस्कनेक्ट हो गया है", + "Undefined": "अनिर्धारित", + "UserOnlineFromDevice": "{0} {1} से ऑनलाइन है", + "Shows": "शो", + "UserPasswordChangedWithName": "उपयोगकर्ता {0} के लिए पासवर्ड बदल दिया गया है", + "UserDeletedWithName": "उपयोगकर्ता {0} हटा दिया गया", + "UserPolicyUpdatedWithName": "{0} के लिए उपयोगकर्ता नीति अपडेट कर दी गई है", + "User": "उपयोगकर्ता", + "SubtitleDownloadFailureFromForItem": "{1} के लिए {0} से उपशीर्षक डाउनलोड करने में विफल", + "ProviderValue": "प्रदाता: {0}", + "ScheduledTaskFailedWithName": "{0}असफल", + "UserLockedOutWithName": "उपयोगकर्ता {0} को लॉक आउट कर दिया गया है", + "System": "प्रणाली", + "TvShows": "टीवी शो", + "HearingImpaired": "मूक बधिर", + "ValueSpecialEpisodeName": "विशेष - {0}", + "TasksMaintenanceCategory": "रखरखाव", + "Sync": "समाकलयति", + "VersionNumber": "{0} पाठान्तर", + "ValueHasBeenAddedToLibrary": "{0} आपके माध्यम ग्रन्थालय में उपजात हो गया हैं", + "TasksLibraryCategory": "संग्रहालय", + "TaskOptimizeDatabase": "जानकारी प्रवृद्धि", + "TaskDownloadMissingSubtitles": "लापता अनुलेख डाउनलोड करें", + "TaskRefreshLibrary": "माध्यम संग्राहत को छाने", + "TaskCleanActivityLog": "क्रियाकलाप लॉग साफ करें", + "TasksChannelsCategory": "इंटरनेट प्रणाली", + "TasksApplicationCategory": "अनुप्रयोग", + "TaskRefreshPeople": "लोगोकी जानकारी ताज़ी करें", + "TaskKeyframeExtractor": "कीफ़्रेम एक्सट्रैक्टर", + "TaskCleanActivityLogDescription": "कॉन्फ़िगर की गई आयु से पुरानी गतिविधि लॉग प्रविष्टियां हटाता है।", + "TaskRefreshChapterImagesDescription": "अध्याय वाले वीडियो के लिए थंबनेल बनाता है।", + "TaskRefreshLibraryDescription": "नई फ़ाइलों के लिए आपकी मीडिया लाइब्रेरी को स्कैन करता है और मेटाडेटा को ताज़ा करता है।", + "TaskCleanLogs": "स्वच्छ लॉग निर्देशिका", + "TaskUpdatePluginsDescription": "प्लगइन्स के लिए अपडेट डाउनलोड और इंस्टॉल करें जो स्वचालित रूप से अपडेट करने के लिए कॉन्फ़िगर किए गए हैं।", + "TaskCleanTranscode": "स्वच्छ ट्रांसकोड निर्देशिका", + "TaskCleanTranscodeDescription": "एक दिन से अधिक पुरानी ट्रांसकोड फ़ाइलें हटाता है.", + "TaskRefreshChannelsDescription": "इंटरनेट चैनल की जानकारी को ताज़ा करता है।", + "TaskOptimizeDatabaseDescription": "डेटाबेस को कॉम्पैक्ट करता है और मुक्त स्थान को छोटा करता है। लाइब्रेरी को स्कैन करने के बाद इस कार्य को चलाने या अन्य परिवर्तन करने से जो डेटाबेस संशोधनों को लागू करते हैं, प्रदर्शन में सुधार कर सकते हैं।", + "TaskRefreshChannels": "इंटरनेट चैनल की जानकारी को ताज़ा करता है", + "TaskRefreshChapterImages": "अध्याय छवियाँ निकालें", + "TaskCleanLogsDescription": "{0} दिन से अधिक पुरानी लॉग फ़ाइलें हटाता है।", + "TaskCleanCacheDescription": "उन कैश फ़ाइलों को हटाता है जिनकी अब सिस्टम को आवश्यकता नहीं है।", + "TaskUpdatePlugins": "अद्यतन प्लगइन्स", + "TaskRefreshPeopleDescription": "आपकी मीडिया लाइब्रेरी में अभिनेताओं और निर्देशकों के लिए मेटाडेटा अपडेट करता है।", + "TaskCleanCache": "स्वच्छ कैश निर्देशिका", + "TaskDownloadMissingSubtitlesDescription": "मेटाडेटा कॉन्फ़िगरेशन के आधार पर लापता उपशीर्षक के लिए इंटरनेट खोजता है।", + "TaskKeyframeExtractorDescription": "अधिक सटीक एचएलएस प्लेलिस्ट बनाने के लिए वीडियो फ़ाइलों से मुख्य-फ़्रेम निकालता है। यह कार्य लंबे समय तक चल सकता है।", + "TaskRefreshTrickplayImages": "ट्रिकप्लै चित्रों को सृजन करे", + "TaskRefreshTrickplayImagesDescription": "नियत संग्रहों में चलचित्रों का ट्रीकप्लै दर्शनों को सृजन करे.", + "TaskAudioNormalization": "श्रव्य सामान्यीकरण", + "TaskAudioNormalizationDescription": "श्रव्य सामान्यीकरण के लिए फाइलें अन्वेषण करें", + "TaskDownloadMissingLyrics": "लापता गानों के बोल डाउनलोड करेँ", + "TaskDownloadMissingLyricsDescription": "गानों के बोल डाउनलोड करता है", + "TaskExtractMediaSegments": "मीडिया सेगमेंट स्कैन", + "TaskExtractMediaSegmentsDescription": "मीडियासेगमेंट सक्षम प्लगइन्स से मीडिया सेगमेंट निकालता है या प्राप्त करता है।", + "TaskMoveTrickplayImages": "ट्रिकप्ले छवि स्थान माइग्रेट करें", + "TaskMoveTrickplayImagesDescription": "लाइब्रेरी सेटिंग्स के अनुसार मौजूदा ट्रिकप्ले फ़ाइलों को स्थानांतरित करता है।", + "TaskCleanCollectionsAndPlaylistsDescription": "संग्रहों और प्लेलिस्टों से उन आइटमों को हटाता है जो अब मौजूद नहीं हैं।", + "TaskCleanCollectionsAndPlaylists": "संग्रह और प्लेलिस्ट साफ़ करें", + "CleanupUserDataTask": "यूज़र डेटा की सफाई करता है।" +} diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json new file mode 100644 index 00000000..94db4357 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/hr.json @@ -0,0 +1,141 @@ +{ + "Albums": "Albumi", + "AppDeviceValues": "Aplikacija: {0}, Uređaj: {1}", + "Application": "Aplikacija", + "Artists": "Izvođači", + "AuthenticationSucceededWithUserName": "{0} uspješno ovjerena", + "Books": "Knjige", + "CameraImageUploadedFrom": "Nova fotografija sa kamere je učitana iz {0}", + "Channels": "Kanali", + "ChapterNameValue": "Poglavlje {0}", + "Collections": "Zbirke", + "DeviceOfflineWithName": "{0} je prekinuo vezu", + "DeviceOnlineWithName": "{0} je povezan", + "FailedLoginAttemptWithUserName": "Neuspješan pokušaj prijave od {0}", + "Favorites": "Favoriti", + "Folders": "Mape", + "Genres": "Žanrovi", + "HeaderAlbumArtists": "Izvođači albuma", + "HeaderContinueWatching": "Nastavi gledati", + "HeaderFavoriteAlbums": "Omiljeni albumi", + "HeaderFavoriteArtists": "Omiljeni izvođači", + "HeaderFavoriteEpisodes": "Omiljene epizode", + "HeaderFavoriteShows": "Omiljene serije", + "HeaderFavoriteSongs": "Omiljene pjesme", + "HeaderLiveTV": "TV uživo", + "HeaderNextUp": "Slijedi", + "HeaderRecordingGroups": "Grupa snimka", + "HomeVideos": "Kućni video", + "Inherit": "Naslijedi", + "ItemAddedWithName": "{0} je dodano u biblioteku", + "ItemRemovedWithName": "{0} je uklonjeno iz biblioteke", + "LabelIpAddressValue": "IP adresa: {0}", + "LabelRunningTimeValue": "Vrijeme rada: {0}", + "Latest": "Najnovije", + "MessageApplicationUpdated": "Jellyfin server je ažuriran", + "MessageApplicationUpdatedTo": "Jellyfin server je ažuriran na {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Dio konfiguracije servera {0} je ažuriran", + "MessageServerConfigurationUpdated": "Konfiguracija servera je ažurirana", + "MixedContent": "Miješani sadržaj", + "Movies": "Filmovi", + "Music": "Glazba", + "MusicVideos": "Glazbeni spotovi", + "NameInstallFailed": "{0} neuspješnih instalacija", + "NameSeasonNumber": "Sezona {0}", + "NameSeasonUnknown": "Sezona nepoznata", + "NewVersionIsAvailable": "Nova verzija Jellyfin servera je dostupna za preuzimanje.", + "NotificationOptionApplicationUpdateAvailable": "Dostupno je ažuriranje aplikacije", + "NotificationOptionApplicationUpdateInstalled": "Instalirano je ažuriranje aplikacije", + "NotificationOptionAudioPlayback": "Reprodukcija glazbe započela", + "NotificationOptionAudioPlaybackStopped": "Reprodukcija glazbe zaustavljena", + "NotificationOptionCameraImageUploaded": "Slika s kamere učitana", + "NotificationOptionInstallationFailed": "Instalacija nije uspjela", + "NotificationOptionNewLibraryContent": "Novi sadržaj dodan", + "NotificationOptionPluginError": "Dodatak zakazao", + "NotificationOptionPluginInstalled": "Dodatak instaliran", + "NotificationOptionPluginUninstalled": "Dodatak deinstaliran", + "NotificationOptionPluginUpdateInstalled": "Instalirano ažuriranje dodatka", + "NotificationOptionServerRestartRequired": "Ponovno pokrenite server", + "NotificationOptionTaskFailed": "Greška zakazanog zadatka", + "NotificationOptionUserLockedOut": "Korisnik zaključan", + "NotificationOptionVideoPlayback": "Reprodukcija videa započela", + "NotificationOptionVideoPlaybackStopped": "Reprodukcija videa zaustavljena", + "Photos": "Fotografije", + "Playlists": "Popisi za reprodukciju", + "Plugin": "Dodatak", + "PluginInstalledWithName": "{0} je instalirano", + "PluginUninstalledWithName": "{0} je deinstalirano", + "PluginUpdatedWithName": "{0} je ažurirano", + "ProviderValue": "Pružatelj: {0}", + "ScheduledTaskFailedWithName": "{0} neuspjelo", + "ScheduledTaskStartedWithName": "{0} pokrenuto", + "ServerNameNeedsToBeRestarted": "{0} treba ponovno pokrenuti", + "Shows": "Emisije", + "Songs": "Pjesme", + "StartupEmbyServerIsLoading": "Jellyfin server se učitava. Pokušajte ponovo uskoro.", + "SubtitleDownloadFailureFromForItem": "Prijevod nije uspješno preuzet od {0} za {1}", + "Sync": "Sinkronizacija", + "System": "Sustav", + "TvShows": "Serije", + "User": "Korisnik", + "UserCreatedWithName": "Korisnik {0} je kreiran", + "UserDeletedWithName": "Korisnik {0} je obrisan", + "UserDownloadingItemWithValues": "{0} preuzima {1}", + "UserLockedOutWithName": "Korisnik {0} je zaključan", + "UserOfflineFromDevice": "{0} prekinuo vezu od {1}", + "UserOnlineFromDevice": "{0} povezan od {1}", + "UserPasswordChangedWithName": "Lozinka je promijenjena za korisnika {0}", + "UserPolicyUpdatedWithName": "Pravila za korisnika ažurirana su za {0}", + "UserStartedPlayingItemWithValues": "{0} je pokrenuo reprodukciju {1} na {2}", + "UserStoppedPlayingItemWithValues": "{0} je završio reprodukciju {1} na {2}", + "ValueHasBeenAddedToLibrary": "{0} je dodano u medijsku biblioteku", + "ValueSpecialEpisodeName": "Posebno - {0}", + "VersionNumber": "Verzija {0}", + "TaskRefreshLibraryDescription": "Skenira medijsku biblioteku radi novih datoteka i osvježava metapodatke.", + "TaskRefreshLibrary": "Skeniraj medijsku biblioteku", + "TaskRefreshChapterImagesDescription": "Kreira sličice za videozapise koji imaju poglavlja.", + "TaskRefreshChapterImages": "Izdvoji slike poglavlja", + "TaskCleanCacheDescription": "Briše nepotrebne datoteke iz predmemorije.", + "TaskCleanCache": "Očisti mapu predmemorije", + "TasksApplicationCategory": "Aplikacija", + "TasksMaintenanceCategory": "Održavanje", + "TaskDownloadMissingSubtitlesDescription": "Pretraži Internet za prijevodima koji nedostaju prema konfiguraciji metapodataka.", + "TaskDownloadMissingSubtitles": "Preuzmi prijevod koji nedostaje", + "TaskRefreshChannelsDescription": "Osvježava informacije Internet kanala.", + "TaskRefreshChannels": "Osvježi kanale", + "TaskCleanTranscodeDescription": "Briše transkodirane datoteke starije od jednog dana.", + "TaskCleanTranscode": "Očisti mapu transkodiranja", + "TaskUpdatePluginsDescription": "Preuzima i instalira ažuriranja za dodatke koji su konfigurirani da se ažuriraju automatski.", + "TaskUpdatePlugins": "Ažuriraj dodatke", + "TaskRefreshPeopleDescription": "Ažurira metapodatke za glumce i redatelje u medijskoj biblioteci.", + "TaskRefreshPeople": "Osvježi osobe", + "TaskCleanLogsDescription": "Briše zapise dnevnika koji su stariji od {0} dana.", + "TaskCleanLogs": "Očisti mapu dnevnika zapisa", + "TasksChannelsCategory": "Internet kanali", + "TasksLibraryCategory": "Biblioteka", + "TaskCleanActivityLogDescription": "Briše zapise dnevnika aktivnosti starije od navedenog vremena.", + "TaskCleanActivityLog": "Očisti dnevnik aktivnosti", + "Undefined": "Nedefinirano", + "Forced": "Forsirani", + "Default": "Zadano", + "TaskOptimizeDatabase": "Optimiziraj bazu podataka", + "External": "Vanjski", + "TaskKeyframeExtractorDescription": "Izvlačenje ključnih okvira iz videozapisa za stvaranje objektivnije HLS liste za reprodukciju. Pokretanje ovog zadatka može potrajati.", + "TaskKeyframeExtractor": "Izvoditelj ključnog okvira", + "TaskOptimizeDatabaseDescription": "Sažima bazu podataka i uklanja prazan prostor. Pokretanje ovog zadatka, može poboljšati performanse nakon provođenja indeksiranja biblioteke ili provođenja drugih promjena koje utječu na bazu podataka.", + "HearingImpaired": "Oštećen sluh", + "TaskRefreshTrickplayImages": "Generiraj slike brzog pregledavanja", + "TaskRefreshTrickplayImagesDescription": "Stvara preglede brzog pregledavanja za videa u aktiviranim bibliotekama.", + "TaskAudioNormalization": "Normalizacija zvuka", + "TaskAudioNormalizationDescription": "Skenira datoteke u potrazi za podacima o normalizaciji zvuka.", + "TaskCleanCollectionsAndPlaylistsDescription": "Uklanja stavke iz zbirki i popisa za reprodukciju koje više ne postoje.", + "TaskCleanCollectionsAndPlaylists": "Očisti zbirke i popise za reprodukciju", + "TaskExtractMediaSegments": "Skeniranje dijelova medija", + "TaskDownloadMissingLyrics": "Preuzmi tekstove koji nedostaju", + "TaskDownloadMissingLyricsDescription": "Preuzmi tekstove pjesama", + "TaskExtractMediaSegmentsDescription": "Izvlači ili pribavlja dijelove medija iz omogućenih media pluginova.", + "TaskMoveTrickplayImages": "Premjesti mjesto slika brzog pregledavanja", + "TaskMoveTrickplayImagesDescription": "Premješta postojeće datoteke brzog pregledavanja prema postavkama biblioteke.", + "CleanupUserDataTask": "Zadatak čišćenja korisničkih podataka", + "CleanupUserDataTaskDescription": "Briše sve korisničke podatke (stanje gledanja, status favorita itd.) s medija koji više nisu prisutni najmanje 90 dana." +} diff --git a/Emby.Server.Implementations/Localization/Core/ht.json b/Emby.Server.Implementations/Localization/Core/ht.json new file mode 100644 index 00000000..f927d317 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ht.json @@ -0,0 +1,62 @@ +{ + "Books": "Liv", + "TasksLibraryCategory": "Libreri", + "Albums": "Albòm yo", + "Artists": "Atis yo", + "Application": "Aplikasyon", + "Channels": "Kanal yo", + "ChapterNameValue": "Chapit {0}", + "Default": "Defo", + "DeviceOnlineWithName": "{0} konekte", + "DeviceOfflineWithName": "{0} dekonekte", + "External": "Extèn", + "Collections": "Koleksyon yo", + "Favorites": "Pi Renmen", + "Folders": "Dosye", + "Genres": "Jan yo", + "Forced": "Fòse", + "HeaderAlbumArtists": "Albòm Atis", + "HeaderContinueWatching": "Kontinye Kade", + "HeaderFavoriteAlbums": "Albòm Pi Renmen", + "HeaderFavoriteArtists": "Atis Pi Renmen", + "HeaderFavoriteEpisodes": "Epizòd Pi Renmen", + "HeaderFavoriteShows": "Emisyon Pi Renmen", + "HeaderFavoriteSongs": "Mizik Pi Renmen", + "HeaderLiveTV": "Televizyon an Direk", + "HeaderNextUp": "Pwochen an", + "HomeVideos": "Videyo Lakay", + "Latest": "Pi Resan", + "MessageApplicationUpdated": "Sèvè Jellyfin met a jou", + "MessageApplicationUpdatedTo": "Sèvè Jellyfin met a jou sou {0}", + "Movies": "Fim", + "MixedContent": "Kontni Melanje", + "Music": "Mizik", + "MusicVideos": "Videyo Mizik", + "NameInstallFailed": "{0} enstalasyon fe fayit", + "NameSeasonNumber": "Sezon {0}", + "NameSeasonUnknown": "Sezon Enkoni", + "NotificationOptionCameraImageUploaded": "Imaj Kamera telechaje", + "NotificationOptionInstallationFailed": "Enstalasyon echwe", + "Photos": "Foto", + "PluginInstalledWithName": "{0} te enstale", + "PluginUninstalledWithName": "{0} te dezenstale", + "PluginUpdatedWithName": "{0} te mi a jou", + "ScheduledTaskFailedWithName": "{0} echwe", + "ScheduledTaskStartedWithName": "{0} komanse", + "Songs": "Mizik yo", + "Shows": "Emisyon yo", + "System": "Sistèm", + "TvShows": "Emisyon Tele", + "User": "Itilizatè", + "UserCreatedWithName": "Itilizatè {0} kreye", + "UserDeletedWithName": "Itilizatè {0} a efase", + "UserDownloadingItemWithValues": "{0} ap telechaje {1}", + "UserOfflineFromDevice": "{0} dekonekte de {1}", + "UserStartedPlayingItemWithValues": "{0} ap jwe {1} sou {2}", + "UserStoppedPlayingItemWithValues": "{0} fin jwe {1} sou {2}", + "UserPasswordChangedWithName": "Modpas la chanje pou Itilizatè {0}", + "ValueSpecialEpisodeName": "Spesyal - {0}", + "VersionNumber": "Vesyon {0}", + "TasksApplicationCategory": "Aplikasyon", + "TasksMaintenanceCategory": "Antretyen" +} diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json new file mode 100644 index 00000000..7d72c1f3 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/hu.json @@ -0,0 +1,141 @@ +{ + "Albums": "Albumok", + "AppDeviceValues": "Program: {0}, Eszköz: {1}", + "Application": "Alkalmazás", + "Artists": "Előadók", + "AuthenticationSucceededWithUserName": "{0} sikeresen hitelesítve", + "Books": "Könyvek", + "CameraImageUploadedFrom": "Új kamerakép lett feltöltve innen: {0}", + "Channels": "Csatornák", + "ChapterNameValue": "{0}. jelenet", + "Collections": "Gyűjtemények", + "DeviceOfflineWithName": "{0} kijelentkezett", + "DeviceOnlineWithName": "{0} belépett", + "FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet innen: {0}", + "Favorites": "Kedvencek", + "Folders": "Mappák", + "Genres": "Műfajok", + "HeaderAlbumArtists": "Albumelőadók", + "HeaderContinueWatching": "Megtekintés folytatása", + "HeaderFavoriteAlbums": "Kedvenc albumok", + "HeaderFavoriteArtists": "Kedvenc előadók", + "HeaderFavoriteEpisodes": "Kedvenc epizódok", + "HeaderFavoriteShows": "Kedvenc sorozatok", + "HeaderFavoriteSongs": "Kedvenc számok", + "HeaderLiveTV": "Élő TV", + "HeaderNextUp": "Következik", + "HeaderRecordingGroups": "Felvételi csoportok", + "HomeVideos": "Otthoni videók", + "Inherit": "Öröklés", + "ItemAddedWithName": "{0} hozzáadva a médiatárhoz", + "ItemRemovedWithName": "{0} eltávolítva a médiatárból", + "LabelIpAddressValue": "IP-cím: {0}", + "LabelRunningTimeValue": "Lejátszási idő: {0}", + "Latest": "Legújabb", + "MessageApplicationUpdated": "A Jellyfin kiszolgáló frissítve lett", + "MessageApplicationUpdatedTo": "A Jellyfin kiszolgáló frissítve lett a következőre: {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "A kiszolgálókonfigurációs rész frissítve lett: {0}", + "MessageServerConfigurationUpdated": "A kiszolgálókonfiguráció frissítve lett", + "MixedContent": "Vegyes tartalom", + "Movies": "Filmek", + "Music": "Zenék", + "MusicVideos": "Zenei videóklipek", + "NameInstallFailed": "{0} sikertelen telepítés", + "NameSeasonNumber": "{0}. évad", + "NameSeasonUnknown": "Ismeretlen évad", + "NewVersionIsAvailable": "Letölthető a Jellyfin kiszolgáló új verziója.", + "NotificationOptionApplicationUpdateAvailable": "Frissítés érhető el az alkalmazáshoz", + "NotificationOptionApplicationUpdateInstalled": "Alkalmazásfrissítés telepítve", + "NotificationOptionAudioPlayback": "Hanglejátszás elkezdődött", + "NotificationOptionAudioPlaybackStopped": "Hanglejátszás leállítva", + "NotificationOptionCameraImageUploaded": "Kamerakép feltöltve", + "NotificationOptionInstallationFailed": "Telepítési hiba", + "NotificationOptionNewLibraryContent": "Új tartalom hozzáadva", + "NotificationOptionPluginError": "Bővítményhiba", + "NotificationOptionPluginInstalled": "Bővítmény telepítve", + "NotificationOptionPluginUninstalled": "Bővítmény eltávolítva", + "NotificationOptionPluginUpdateInstalled": "Bővítményfrissítés telepítve", + "NotificationOptionServerRestartRequired": "A szerver újraindítása szükséges", + "NotificationOptionTaskFailed": "Hiba az ütemezett feladatban", + "NotificationOptionUserLockedOut": "Felhasználó tiltva", + "NotificationOptionVideoPlayback": "Videólejátszás elkezdve", + "NotificationOptionVideoPlaybackStopped": "Videólejátszás leállítva", + "Photos": "Fényképek", + "Playlists": "Lejátszási listák", + "Plugin": "Bővítmény", + "PluginInstalledWithName": "{0} telepítve", + "PluginUninstalledWithName": "{0} eltávolítva", + "PluginUpdatedWithName": "{0} frissítve", + "ProviderValue": "Szolgáltató: {0}", + "ScheduledTaskFailedWithName": "{0} sikertelen", + "ScheduledTaskStartedWithName": "{0} elkezdve", + "ServerNameNeedsToBeRestarted": "A(z) {0} újraindítása szükséges", + "Shows": "Sorozatok", + "Songs": "Számok", + "StartupEmbyServerIsLoading": "A Jellyfin kiszolgáló betöltődik. Próbálja újra hamarosan.", + "SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen: {0}, ehhez: {1}", + "Sync": "Szinkronizálás", + "System": "Rendszer", + "TvShows": "TV műsorok", + "User": "Felhasználó", + "UserCreatedWithName": "{0} felhasználó létrehozva", + "UserDeletedWithName": "{0} felhasználó törölve", + "UserDownloadingItemWithValues": "{0} letölti: {1}", + "UserLockedOutWithName": "{0} felhasználó zárolva van", + "UserOfflineFromDevice": "{0} kijelentkezett innen: {1}", + "UserOnlineFromDevice": "{0} online innen: {1}", + "UserPasswordChangedWithName": "{0} jelszava megváltozott", + "UserPolicyUpdatedWithName": "{0} felhasználói házirendje frissült", + "UserStartedPlayingItemWithValues": "{0} elkezdte lejátszani a következőt: {1}, itt: {2}", + "UserStoppedPlayingItemWithValues": "{0} befejezte a következő lejátszását: {1}, itt: {2}", + "ValueHasBeenAddedToLibrary": "{0} hozzáadva a médiatárhoz", + "ValueSpecialEpisodeName": "Különkiadás – {0}", + "VersionNumber": "Verzió: {0}", + "TaskCleanTranscode": "Átkódolási könyvtár ürítése", + "TaskUpdatePluginsDescription": "Letölti és telepíti a frissítéseket azokhoz a bővítményekhez, amelyeknél az automatikus frissítés engedélyezve van.", + "TaskUpdatePlugins": "Bővítmények frissítése", + "TaskRefreshPeopleDescription": "Frissíti a szereplők és a stábok metaadatait a médiatárban.", + "TaskRefreshPeople": "Személyek frissítése", + "TaskCleanLogsDescription": "Törli azokat a naplófájlokat, amelyek {0} napnál régebbiek.", + "TaskCleanLogs": "Naplózási könyvtár ürítése", + "TaskRefreshLibraryDescription": "Átvizsgálja a médiatárat új fájlokat keresve, és frissíti a metaadatokat.", + "TaskRefreshLibrary": "Médiatár átvizsgálása", + "TaskRefreshChapterImagesDescription": "Miniatűröket hoz létre az olyan videókhoz, amely tartalmaz fejezeteket.", + "TaskRefreshChapterImages": "Fejezetképek kinyerése", + "TaskCleanCacheDescription": "Törli azokat a gyorsítótárazott fájlokat, amikre a rendszernek már nincs szüksége.", + "TaskCleanCache": "Gyorsítótár könyvtárának ürítése", + "TasksChannelsCategory": "Internetes csatornák", + "TasksApplicationCategory": "Alkalmazás", + "TasksLibraryCategory": "Médiatár", + "TasksMaintenanceCategory": "Karbantartás", + "TaskDownloadMissingSubtitlesDescription": "A metaadat-konfiguráció alapján ellenőrzi és letölti a hiányzó feliratokat az internetről.", + "TaskDownloadMissingSubtitles": "Hiányzó feliratok letöltése", + "TaskRefreshChannelsDescription": "Frissíti az internetes csatornák adatait.", + "TaskRefreshChannels": "Csatornák frissítése", + "TaskCleanTranscodeDescription": "Törli az egy napnál régebbi átkódolási fájlokat.", + "TaskCleanActivityLogDescription": "A beállítottnál korábbi bejegyzések törlése a tevékenységnaplóból.", + "TaskCleanActivityLog": "Tevékenységnapló törlése", + "Undefined": "Meghatározatlan", + "Forced": "Kényszerített", + "Default": "Alapértelmezett", + "TaskOptimizeDatabaseDescription": "Tömöríti az adatbázist és csonkolja a szabad helyet. A feladat futtatása a médiatár beolvasása, vagy egyéb adatbázis-módosítást igénylő változtatás végrehajtása után, javíthatja a teljesítményt.", + "TaskOptimizeDatabase": "Adatbázis optimalizálása", + "TaskKeyframeExtractor": "Kulcsképkockák kibontása", + "TaskKeyframeExtractorDescription": "Kibontja a kulcsképkockákat a videófájlokból, hogy pontosabb HLS lejátszási listákat hozzon létre. Ez a feladat hosszú ideig tarthat.", + "External": "Külső", + "HearingImpaired": "Hallássérült", + "TaskRefreshTrickplayImages": "Trickplay képek előállítása", + "TaskRefreshTrickplayImagesDescription": "Trickplay előnézetet készít az engedélyezett könyvtárakban lévő videókhoz.", + "TaskAudioNormalization": "Hangerő-normalizálás", + "TaskCleanCollectionsAndPlaylistsDescription": "Nem létező elemek törlése a gyűjteményekből és lejátszási listákról.", + "TaskAudioNormalizationDescription": "Hangerő-normalizálási adatok keresése.", + "TaskCleanCollectionsAndPlaylists": "Gyűjtemények és lejátszási listák optimalizálása", + "TaskExtractMediaSegments": "Médiaszegmens felismerése", + "TaskDownloadMissingLyrics": "Hiányzó szöveg letöltése", + "TaskDownloadMissingLyricsDescription": "Zenék szövegének letöltése", + "TaskMoveTrickplayImages": "Trickplay képek helyének átköltöztetése", + "TaskMoveTrickplayImagesDescription": "A médiatár-beállításoknak megfelelően áthelyezi a meglévő trickplay fájlokat.", + "TaskExtractMediaSegmentsDescription": "Kinyeri vagy megszerzi a médiaszegmenseket a MediaSegment támogatással rendelkező bővítményekből.", + "CleanupUserDataTaskDescription": "Legalább 90 napja nem elérhető médiákhoz kapcsolódó összes felhasználói adat (pl. megtekintési állapot, kedvencek) törlése.", + "CleanupUserDataTask": "Felhasználói adatok tisztítása feladat" +} diff --git a/Emby.Server.Implementations/Localization/Core/hy.json b/Emby.Server.Implementations/Localization/Core/hy.json new file mode 100644 index 00000000..563f8429 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/hy.json @@ -0,0 +1,39 @@ +{ + "TasksLibraryCategory": "Գրադարան", + "TasksApplicationCategory": "Հավելված", + "TaskCleanActivityLog": "Մաքրել ակտիվության մատյանը", + "Application": "Հավելված", + "AuthenticationSucceededWithUserName": "{0} հաջողությամբ վավերականացվել են", + "Books": "Գրքեր", + "CameraImageUploadedFrom": "Նոր լուսանկար է վերբեռնվել {0}-ի կողմից", + "Channels": "Ալիքներ", + "DeviceOfflineWithName": "{0}ը անջատվեց", + "External": "Արտաքին", + "FailedLoginAttemptWithUserName": "Ձախողված մուտքի փործ {0}-ի կողմից", + "Folders": "Պանակներ", + "HeaderContinueWatching": "Շարունակել դիտումը", + "Inherit": "Ժառանգել", + "ItemAddedWithName": "{0}ը ավացված է գրադարանի մեջ", + "ItemRemovedWithName": "{0}ը հեռացված է գրադարանից", + "LabelIpAddressValue": "IP հասցե` {0}", + "Movies": "Ֆիլմեր", + "Music": "Երաժշտություն", + "NameSeasonNumber": "Սեզոն {0}", + "Photos": "Լուսանկարներ", + "PluginInstalledWithName": "{0}ն տեղադրված է", + "Songs": "Երգեր", + "System": "Համակարգ", + "TvShows": "Հեռուստասերիալներ", + "User": "Օգտատեր", + "VersionNumber": "Տարբերակ {0}", + "TasksMaintenanceCategory": "Սպասարկում", + "TasksChannelsCategory": "Ինտերնետային ալիքներ", + "TaskRefreshPeople": "Թարմացնել մարդկանց", + "TaskRefreshChannels": "Թարմացնել ալիքները", + "TaskDownloadMissingSubtitles": "Ներբեռնել պակասող ենթագրերը", + "Albums": "Ալբոմներ", + "AppDeviceValues": "Հավելված` {0}, Սարք `{1}", + "ChapterNameValue": "Գլուխ {0}", + "Collections": "Հավաքածուներ", + "DeviceOnlineWithName": "{0}-ն միացված է" +} diff --git a/Emby.Server.Implementations/Localization/Core/id.json b/Emby.Server.Implementations/Localization/Core/id.json new file mode 100644 index 00000000..2a428168 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/id.json @@ -0,0 +1,141 @@ +{ + "Albums": "Album", + "AuthenticationSucceededWithUserName": "{0} berhasil diautentikasi", + "AppDeviceValues": "Aplikasi : {0}, Perangkat : {1}", + "LabelRunningTimeValue": "Waktu berjalan: {0}", + "MessageApplicationUpdatedTo": "Jellyfin Server sudah diperbarui ke {0}", + "MessageApplicationUpdated": "Jellyfin Server sudah diperbarui", + "Latest": "Terbaru", + "LabelIpAddressValue": "Alamat IP: {0}", + "ItemRemovedWithName": "{0} sudah dihapus dari pustaka", + "ItemAddedWithName": "{0} telah dimasukkan ke dalam pustaka", + "Inherit": "Warisi", + "HomeVideos": "Video Rumahan", + "HeaderRecordingGroups": "Grup Rekaman", + "HeaderNextUp": "Selanjutnya", + "HeaderLiveTV": "Siaran langsung", + "HeaderFavoriteSongs": "Lagu Favorit", + "HeaderFavoriteShows": "Tayangan Favorit", + "HeaderFavoriteEpisodes": "Episode Favorit", + "HeaderFavoriteArtists": "Artis Favorit", + "HeaderFavoriteAlbums": "Album Favorit", + "HeaderContinueWatching": "Lanjut Menonton", + "HeaderAlbumArtists": "Album Artis", + "Genres": "Aliran", + "Folders": "Folder", + "Favorites": "Favorit", + "Collections": "Koleksi", + "Books": "Buku", + "Artists": "Artis", + "Application": "Aplikasi", + "ChapterNameValue": "Bagian {0}", + "Channels": "Saluran", + "TvShows": "Seri TV", + "SubtitleDownloadFailureFromForItem": "Subtitel gagal diunduh dari {0} untuk {1}", + "StartupEmbyServerIsLoading": "Server Jellyfin sedang dimuat. Silakan coba lagi nanti.", + "Songs": "Lagu", + "Playlists": "Daftar putar", + "NotificationOptionPluginUninstalled": "Plugin dihapus", + "MusicVideos": "Video Musik", + "VersionNumber": "Versi {0}", + "ValueSpecialEpisodeName": "Spesial - {0}", + "ValueHasBeenAddedToLibrary": "{0} telah ditambahkan ke pustaka media Anda", + "UserStoppedPlayingItemWithValues": "{0} telah selesai memutar {1} pada {2}", + "UserStartedPlayingItemWithValues": "{0} sedang memutar {1} pada {2}", + "UserPolicyUpdatedWithName": "Kebijakan pengguna telah diperbarui untuk {0}", + "UserPasswordChangedWithName": "Kata sandi telah diubah untuk pengguna {0}", + "UserOnlineFromDevice": "{0} sedang daring dari {1}", + "UserOfflineFromDevice": "{0} telah terputus dari {1}", + "UserLockedOutWithName": "Pengguna {0} telah dikunci", + "UserDownloadingItemWithValues": "{0} sedang mengunduh {1}", + "UserDeletedWithName": "Pengguna {0} telah dihapus", + "UserCreatedWithName": "Pengguna {0} telah dibuat", + "User": "Pengguna", + "System": "Sistem", + "Sync": "Sinkron", + "Shows": "Tayangan", + "ServerNameNeedsToBeRestarted": "{0} perlu dimuat ulang", + "ScheduledTaskStartedWithName": "{0} dimulai", + "ScheduledTaskFailedWithName": "{0} gagal", + "ProviderValue": "Penyedia: {0}", + "PluginUpdatedWithName": "{0} telah diperbarui", + "PluginInstalledWithName": "{0} telah dipasang", + "Plugin": "Plugin", + "Photos": "Foto", + "NotificationOptionUserLockedOut": "Pengguna terkunci", + "NotificationOptionTaskFailed": "Kegagalan tugas terjadwal", + "NotificationOptionServerRestartRequired": "Muat ulang server dibutuhkan", + "NotificationOptionPluginUpdateInstalled": "Pembaruan plugin terpasang", + "NotificationOptionPluginInstalled": "Plugin terpasang", + "NotificationOptionPluginError": "Kegagalan plugin", + "NotificationOptionNewLibraryContent": "Konten baru ditambahkan", + "NotificationOptionInstallationFailed": "Kegagalan pemasangan", + "NotificationOptionCameraImageUploaded": "Gambar kamera terunggah", + "NotificationOptionApplicationUpdateInstalled": "Pembaruan aplikasi terpasang", + "NotificationOptionApplicationUpdateAvailable": "Pembaruan aplikasi tersedia", + "NewVersionIsAvailable": "Versi baru dari Jellyfin Server sudah tersedia untuk diunduh.", + "NameSeasonUnknown": "Musim tak diketahui", + "NameSeasonNumber": "Musim {0}", + "NameInstallFailed": "{0} penginstalan gagal", + "Music": "Musik", + "Movies": "Film", + "MessageServerConfigurationUpdated": "Konfigurasi server telah diperbarui", + "MessageNamedServerConfigurationUpdatedWithValue": "Bagian konfigurasi server {0} telah diperbarui", + "FailedLoginAttemptWithUserName": "Gagal upaya login dari {0}", + "CameraImageUploadedFrom": "Sebuah gambar kamera baru telah diunggah dari {0}", + "DeviceOfflineWithName": "{0} telah terputus", + "DeviceOnlineWithName": "{0} telah terhubung", + "NotificationOptionVideoPlaybackStopped": "Pemutaran video berhenti", + "NotificationOptionVideoPlayback": "Pemutaran video dimulai", + "NotificationOptionAudioPlaybackStopped": "Pemutaran audio berhenti", + "NotificationOptionAudioPlayback": "Pemutaran audio dimulai", + "MixedContent": "Konten campuran", + "PluginUninstalledWithName": "{0} telah dihapus", + "TaskRefreshChapterImagesDescription": "Membuat gambar mini untuk video yang memiliki bagian.", + "TaskRefreshChapterImages": "Ekstrak Gambar Bagian", + "TaskCleanCacheDescription": "Menghapus file cache yang tidak lagi dibutuhkan oleh sistem.", + "TaskCleanCache": "Bersihkan Cache Direktori", + "TasksLibraryCategory": "Pustaka", + "TasksMaintenanceCategory": "Perbaikan", + "TasksApplicationCategory": "Aplikasi", + "TaskRefreshPeopleDescription": "Memperbarui metadata untuk aktor dan sutradara di pustaka media Anda.", + "TaskRefreshLibraryDescription": "Memindai Pustaka media Anda untuk mencari file baru dan memperbarui metadata.", + "TasksChannelsCategory": "Saluran Online", + "TaskDownloadMissingSubtitlesDescription": "Mencari di internet untuk subtitle yang hilang berdasarkan konfigurasi metadata.", + "TaskDownloadMissingSubtitles": "Unduh subtitle yang hilang", + "TaskRefreshChannelsDescription": "Segarkan informasi saluran internet.", + "TaskRefreshChannels": "Segarkan Saluran", + "TaskCleanTranscodeDescription": "Menghapus file transcode yang berumur lebih dari satu hari.", + "TaskCleanTranscode": "Bersihkan Direktori Transcode", + "TaskUpdatePluginsDescription": "Unduh dan instal pembaruan untuk plugin yang dikonfigurasi untuk memperbarui secara otomatis.", + "TaskUpdatePlugins": "Perbarui Plugin", + "TaskRefreshPeople": "Muat ulang Orang", + "TaskCleanLogsDescription": "Menghapus file log yang lebih dari {0} hari.", + "TaskCleanLogs": "Bersihkan Log Direktori", + "TaskRefreshLibrary": "Pindai Pustaka Media", + "TaskCleanActivityLogDescription": "Menghapus log aktivitas yang lebih tua dari umur yang dikonfigurasi.", + "TaskCleanActivityLog": "Bersihkan Log Aktivitas", + "Undefined": "Tidak terdefinisi", + "Forced": "Dipaksa", + "Default": "Bawaan", + "TaskOptimizeDatabaseDescription": "Rapihkan basis data dan membersihkan ruang kosong. Menjalankan tugas ini setelah memindai pustaka atau melakukan perubahan lain yang menyiratkan modifikasi basis data dapat meningkatkan kinerja.", + "TaskOptimizeDatabase": "Optimalkan basis data", + "TaskKeyframeExtractorDescription": "Ekstrak bingkai utama dari file video untuk membuat daftar putar HLS yang lebih tepat. Tugas ini dapat berjalan untuk waktu yang lama.", + "TaskKeyframeExtractor": "Ekstraktor Bingkai Utama", + "External": "Luar", + "HearingImpaired": "Gangguan Pendengaran", + "TaskRefreshTrickplayImages": "Hasilkan Gambar Trickplay", + "TaskRefreshTrickplayImagesDescription": "Buat pratinjau trickplay untuk video di perpustakaan yang diaktifkan.", + "TaskAudioNormalizationDescription": "Pindai file untuk data normalisasi audio.", + "TaskAudioNormalization": "Normalisasi Audio", + "TaskCleanCollectionsAndPlaylists": "Bersihkan koleksi dan daftar putar", + "TaskCleanCollectionsAndPlaylistsDescription": "Menghapus item dari koleksi dan daftar putar yang sudah tidak ada.", + "TaskDownloadMissingLyricsDescription": "Unduh lirik untuk lagu", + "TaskExtractMediaSegmentsDescription": "Mengekstrak atau memperoleh segmen media dari plugin yang mendukung MediaSegment.", + "TaskMoveTrickplayImagesDescription": "Memindahkan file trickplay yang sudah ada sesuai dengan pengaturan pustaka.", + "CleanupUserDataTaskDescription": "Membersihkan semua data pengguna (status tontonan, status favorit, dll.) dari media yang sudah tidak ada selama setidaknya 90 hari.", + "TaskExtractMediaSegments": "Scan Segmen media", + "TaskMoveTrickplayImages": "Migrasikan Lokasi Gambar Trickplay", + "TaskDownloadMissingLyrics": "Unduh Lirik yang Hilang", + "CleanupUserDataTask": "Tugas Pembersihan Data Pengguna" +} diff --git a/Emby.Server.Implementations/Localization/Core/is.json b/Emby.Server.Implementations/Localization/Core/is.json new file mode 100644 index 00000000..6f94df9d --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/is.json @@ -0,0 +1,138 @@ +{ + "LabelIpAddressValue": "IP tala: {0}", + "ItemRemovedWithName": "{0} var fjarlægt úr safninu", + "ItemAddedWithName": "{0} var bætt í safnið", + "Inherit": "Erfa", + "HomeVideos": "Heimamyndbönd", + "HeaderRecordingGroups": "Upptökuhópar", + "HeaderNextUp": "Næst á dagskrá", + "HeaderLiveTV": "Sjónvarp í beinni útsendingu", + "HeaderFavoriteSongs": "Uppáhalds Lög", + "HeaderFavoriteShows": "Uppáhalds Sjónvarpsþættir", + "HeaderFavoriteEpisodes": "Uppáhalds Þættir", + "HeaderFavoriteArtists": "Uppáhalds Listamenn", + "HeaderFavoriteAlbums": "Uppáhalds Plötur", + "HeaderContinueWatching": "Halda áfram að horfa", + "HeaderAlbumArtists": "Listamaður á umslagi", + "Genres": "Stefnur", + "Folders": "Möppur", + "Favorites": "Uppáhalds", + "FailedLoginAttemptWithUserName": "{0} mistókst að auðkenna sig", + "DeviceOnlineWithName": "{0} hefur tengst", + "DeviceOfflineWithName": "{0} hefur aftengst", + "Collections": "Söfn", + "ChapterNameValue": "Kafli {0}", + "Channels": "Rásir", + "CameraImageUploadedFrom": "{0} hefur hlaðið upp nýrri ljósmynd úr myndavél sinni", + "Books": "Bækur", + "AuthenticationSucceededWithUserName": "Auðkenning fyrir {0} tókst", + "Artists": "Listamenn", + "Application": "Forrit", + "AppDeviceValues": "Snjallforrit: {0}, Tæki: {1}", + "Albums": "Plötur", + "Plugin": "Viðbótarvirkni", + "Photos": "Ljósmyndir", + "NotificationOptionVideoPlaybackStopped": "Myndbandsafspilun stöðvuð", + "NotificationOptionVideoPlayback": "Myndbandsafspilun hafin", + "NotificationOptionUserLockedOut": "Notandi læstur úti", + "NotificationOptionServerRestartRequired": "Endurræsing þjóns er nauðsynleg", + "NotificationOptionPluginUpdateInstalled": "Uppfærslu á viðbótarvirkni lokið", + "NotificationOptionPluginUninstalled": "Viðbótarvirkni fjarlægð", + "NotificationOptionPluginInstalled": "Viðbótarvirkni sett upp", + "NotificationOptionPluginError": "Bilun í viðbót", + "NotificationOptionInstallationFailed": "Uppsetning tókst ekki", + "NotificationOptionCameraImageUploaded": "Ljósmynd hlaðið upp", + "NotificationOptionAudioPlaybackStopped": "Hljóðafspilun stöðvuð", + "NotificationOptionAudioPlayback": "Hljóðafspilun hafin", + "NotificationOptionApplicationUpdateInstalled": "Uppfærsla uppsett", + "NotificationOptionApplicationUpdateAvailable": "Uppfærsla í boði", + "NameSeasonUnknown": "Þáttaröð óþekkt", + "NameSeasonNumber": "Þáttaröð {0}", + "MixedContent": "Blandað efni", + "MessageServerConfigurationUpdated": "Stillingar þjóns hafa verið uppfærðar", + "MessageApplicationUpdatedTo": "Jellyfin þjónn hefur verið uppfærður í {0}", + "MessageApplicationUpdated": "Jellyfin þjónn hefur verið uppfærður", + "Latest": "Nýjasta", + "LabelRunningTimeValue": "spilunartími: {0}", + "User": "Notandi", + "System": "Kerfi", + "NotificationOptionNewLibraryContent": "Nýju efni bætt við", + "NewVersionIsAvailable": "Ný útgáfa af Jellyfin þjón er tilbúin til niðurhals.", + "NameInstallFailed": "{0} uppsetning mistókst", + "MusicVideos": "Tónlistarmyndbönd", + "Music": "Tónlist", + "Movies": "Kvikmyndir", + "UserDeletedWithName": "Notanda {0} hefur verið eytt", + "UserCreatedWithName": "Notandi {0} hefur verið stofnaður", + "TvShows": "Sjónvarpsþættir", + "Sync": "Samstilla", + "Songs": "Lög", + "ServerNameNeedsToBeRestarted": "{0} þarf að vera endurræstur", + "ScheduledTaskStartedWithName": "{0} hafin", + "ScheduledTaskFailedWithName": "{0} mistókst", + "PluginUpdatedWithName": "{0} var uppfært", + "PluginUninstalledWithName": "{0} var fjarlægt", + "PluginInstalledWithName": "{0} var sett upp", + "NotificationOptionTaskFailed": "Tímasett verkefni mistókst", + "StartupEmbyServerIsLoading": "Jellyfin netþjónnin er að ræsa sig upp. Vinsamlegast reyndu aftur fljótlega.", + "VersionNumber": "Útgáfa {0}", + "ValueHasBeenAddedToLibrary": "{0} hefur verið bætt við í gagnasafnið þitt", + "UserStoppedPlayingItemWithValues": "{0} hefur lokið spilunar af {1} á {2}", + "UserStartedPlayingItemWithValues": "{0} er að spila {1} á {2}", + "UserPolicyUpdatedWithName": "Notandaregla hefur verið uppfærð fyrir {0}", + "UserPasswordChangedWithName": "Lykilorði fyrir notandann {0} hefur verið breytt", + "UserOnlineFromDevice": "{0} hefur verið virkur síðan {1}", + "UserOfflineFromDevice": "{0} hefur aftengst frá {1}", + "UserLockedOutWithName": "Notandi {0} hefur verið læstur úti", + "UserDownloadingItemWithValues": "{0} hleður niður {1}", + "SubtitleDownloadFailureFromForItem": "Tókst ekki að hala niður skjátextum frá {0} til {1}", + "ProviderValue": "Efnisveita: {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Stilling {0} hefur verið uppfærð á netþjón", + "ValueSpecialEpisodeName": "Sérstaktur - {0}", + "Shows": "Þættir", + "Playlists": "Efnisskrár", + "TaskRefreshChannelsDescription": "Endurhlaða upplýsingum netrása.", + "TaskRefreshChannels": "Endurhlaða Rásir", + "TaskCleanTranscodeDescription": "Eyða umkóðuðum skrám sem eru meira en einum degi eldri.", + "TaskCleanTranscode": "Hreinsa Umkóðunarmöppu", + "TaskUpdatePluginsDescription": "Sækja og setja upp uppfærslur fyrir viðbætur sem eru stilltar til að uppfæra sjálfkrafa.", + "TaskUpdatePlugins": "Uppfæra viðbætur", + "TaskRefreshPeopleDescription": "Uppfærir lýsigögn fyrir leikara og leikstjóra í miðlasafninu þínu.", + "TaskRefreshLibraryDescription": "Skannar miðlasafnið þitt fyrir nýjum skrám og uppfærir lýsigögn.", + "TaskRefreshLibrary": "Skanna miðlasafn", + "TaskRefreshChapterImagesDescription": "Býr til smámyndir fyrir myndbönd sem hafa kaflaskil.", + "TaskCleanCacheDescription": "Eyðir skrám í skyndiminni sem ekki er lengur þörf fyrir í kerfinu.", + "TaskCleanCache": "Hreinsa skráasafn skyndiminnis", + "TasksChannelsCategory": "Netrásir", + "TasksApplicationCategory": "Forrit", + "TasksLibraryCategory": "Miðlasafn", + "TasksMaintenanceCategory": "Viðhald", + "Default": "Sjálfgefið", + "TaskCleanActivityLog": "Hreinsa athafnaskrá", + "TaskRefreshPeople": "Endurnýja fólk", + "TaskDownloadMissingSubtitles": "Sækja texta sem vantar", + "TaskOptimizeDatabase": "Fínstilla gagnagrunn", + "Undefined": "Óskilgreint", + "TaskCleanLogsDescription": "Eyðir færslu skrám sem eru meira en {0} gömul.", + "TaskCleanLogs": "Hreinsa færslu skrá", + "TaskDownloadMissingSubtitlesDescription": "Leitar á netinu að texta sem vantar miðað við uppsetningu lýsigagna.", + "HearingImpaired": "Heyrnarskertur", + "TaskOptimizeDatabaseDescription": "Þjappar gagnagrunni og bætir við lausu diskaplássi. Að keyra þessa aðgerð eftir skönnun safnsins, eða eftir einhverjar breytingar sem fela í sér gagnagrunnsbreytingar, gætu aukið hraðvirkni.", + "TaskKeyframeExtractor": "Lykilrammaplokkari", + "TaskKeyframeExtractorDescription": "Plokkar lykilramma úr myndbandsskrám til að búa til nákvæmari HLS uppskiptingarlista. Þetta verk getur tekið langan tíma.", + "TaskRefreshChapterImages": "Plokka kafla-myndir", + "TaskCleanActivityLogDescription": "Eyðir virkniskráningarfærslum sem hafa náð settum hámarksaldri.", + "Forced": "Þvingað", + "External": "Útvær", + "TaskRefreshTrickplayImagesDescription": "Býr til hraðspilunarmyndir fyrir myndbönd í virkum söfnum.", + "TaskRefreshTrickplayImages": "Búa til hraðspilunarmyndir", + "TaskAudioNormalization": "Hljóðstöðlun", + "TaskAudioNormalizationDescription": "Leitar að hljóðstöðlunargögnum í skrám.", + "TaskCleanCollectionsAndPlaylists": "Hreinsa söfn og spilunarlista", + "TaskCleanCollectionsAndPlaylistsDescription": "Fjarlægir hluti úr söfnum og spilalistum sem eru ekki lengur til.", + "TaskDownloadMissingLyricsDescription": "Sækja söngtexta fyrir lög", + "TaskDownloadMissingLyrics": "Sækja söngtexta sem vantar", + "TaskExtractMediaSegments": "Skönnun efnishluta", + "CleanupUserDataTask": "Hreinsun notendagagna", + "CleanupUserDataTaskDescription": "Hreinsar öll notendagögn (spilunarstöðu, uppáhöld o.s.frv.) um gögn sem hafa ekki verið til staðar í að lámarki 90 daga." +} diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json new file mode 100644 index 00000000..f0c4b502 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -0,0 +1,141 @@ +{ + "Albums": "Album", + "AppDeviceValues": "App: {0}, Dispositivo: {1}", + "Application": "Applicazione", + "Artists": "Artisti", + "AuthenticationSucceededWithUserName": "{0} autenticato correttamente", + "Books": "Libri", + "CameraImageUploadedFrom": "È stata caricata una nuova fotografia da {0}", + "Channels": "Canali", + "ChapterNameValue": "Capitolo {0}", + "Collections": "Collezioni", + "DeviceOfflineWithName": "{0} si è disconnesso", + "DeviceOnlineWithName": "{0} è connesso", + "FailedLoginAttemptWithUserName": "Tentativo di accesso non riuscito da {0}", + "Favorites": "Preferiti", + "Folders": "Cartelle", + "Genres": "Generi", + "HeaderAlbumArtists": "Artisti dell'album", + "HeaderContinueWatching": "Continua a guardare", + "HeaderFavoriteAlbums": "Album preferiti", + "HeaderFavoriteArtists": "Artisti preferiti", + "HeaderFavoriteEpisodes": "Episodi preferiti", + "HeaderFavoriteShows": "Serie TV preferite", + "HeaderFavoriteSongs": "Brani preferiti", + "HeaderLiveTV": "Diretta TV", + "HeaderNextUp": "Prossimo", + "HeaderRecordingGroups": "Gruppi di registrazione", + "HomeVideos": "Video personali", + "Inherit": "Eredita", + "ItemAddedWithName": "{0} è stato aggiunto alla libreria", + "ItemRemovedWithName": "{0} è stato rimosso dalla libreria", + "LabelIpAddressValue": "Indirizzo IP: {0}", + "LabelRunningTimeValue": "Durata: {0}", + "Latest": "Novità", + "MessageApplicationUpdated": "Jellyfin Server è stato aggiornato", + "MessageApplicationUpdatedTo": "Jellyfin Server è stato aggiornato a {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "La sezione {0} della configurazione server è stata aggiornata", + "MessageServerConfigurationUpdated": "La configurazione del server è stata aggiornata", + "MixedContent": "Contenuto misto", + "Movies": "Film", + "Music": "Musica", + "MusicVideos": "Video musicali", + "NameInstallFailed": "{0} installazione non riuscita", + "NameSeasonNumber": "Stagione {0}", + "NameSeasonUnknown": "Stagione sconosciuta", + "NewVersionIsAvailable": "Una nuova versione di Jellyfin Server è disponibile per il download.", + "NotificationOptionApplicationUpdateAvailable": "Aggiornamento dell'applicazione disponibile", + "NotificationOptionApplicationUpdateInstalled": "Aggiornamento dell'applicazione installato", + "NotificationOptionAudioPlayback": "La riproduzione audio è iniziata", + "NotificationOptionAudioPlaybackStopped": "La riproduzione audio è stata interrotta", + "NotificationOptionCameraImageUploaded": "Immagine fotocamera caricata", + "NotificationOptionInstallationFailed": "Installazione non riuscita", + "NotificationOptionNewLibraryContent": "Nuovo contenuto aggiunto", + "NotificationOptionPluginError": "Errore del plugin", + "NotificationOptionPluginInstalled": "Plugin installato", + "NotificationOptionPluginUninstalled": "Plugin disinstallato", + "NotificationOptionPluginUpdateInstalled": "Aggiornamento plugin installato", + "NotificationOptionServerRestartRequired": "Riavvio del server necessario", + "NotificationOptionTaskFailed": "Operazione pianificata non riuscita", + "NotificationOptionUserLockedOut": "Utente bloccato", + "NotificationOptionVideoPlayback": "Riproduzione video iniziata", + "NotificationOptionVideoPlaybackStopped": "Riproduzione video interrotta", + "Photos": "Foto", + "Playlists": "Scalette", + "Plugin": "Plugin", + "PluginInstalledWithName": "{0} è stato installato", + "PluginUninstalledWithName": "{0} è stato disinstallato", + "PluginUpdatedWithName": "{0} è stato aggiornato", + "ProviderValue": "Provider: {0}", + "ScheduledTaskFailedWithName": "{0} non riuscito", + "ScheduledTaskStartedWithName": "{0} avviato", + "ServerNameNeedsToBeRestarted": "{0} deve essere riavviato", + "Shows": "Serie TV", + "Songs": "Brani", + "StartupEmbyServerIsLoading": "Jellyfin Server si sta avviando. Riprova più tardi.", + "SubtitleDownloadFailureFromForItem": "Impossibile scaricare i sottotitoli da {0} per {1}", + "Sync": "Sincronizza", + "System": "Sistema", + "TvShows": "Serie TV", + "User": "Utente", + "UserCreatedWithName": "L'utente {0} è stato creato", + "UserDeletedWithName": "L'utente {0} è stato eliminato", + "UserDownloadingItemWithValues": "{0} sta scaricando {1}", + "UserLockedOutWithName": "L'utente {0} è stato bloccato", + "UserOfflineFromDevice": "{0} si è disconnesso da {1}", + "UserOnlineFromDevice": "{0} è online su {1}", + "UserPasswordChangedWithName": "La password è stata cambiata per l'utente {0}", + "UserPolicyUpdatedWithName": "La policy dell'utente è stata aggiornata per {0}", + "UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di {1} su {2}", + "UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1} su {2}", + "ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale", + "ValueSpecialEpisodeName": "Speciale - {0}", + "VersionNumber": "Versione {0}", + "TaskRefreshChannelsDescription": "Aggiorna le informazioni dei canali internet.", + "TaskDownloadMissingSubtitlesDescription": "Cerca su internet i sottotitoli mancanti basandosi sulle configurazioni dei metadati.", + "TaskDownloadMissingSubtitles": "Scarica i sottotitoli mancanti", + "TaskRefreshChannels": "Aggiorna canali", + "TaskCleanTranscodeDescription": "Cancella i file di transcodifica più vecchi di un giorno.", + "TaskCleanTranscode": "Svuota la cartella della transcodifica", + "TaskUpdatePluginsDescription": "Scarica e installa gli aggiornamenti per i plugin configurati per l'aggiornamento automatico.", + "TaskUpdatePlugins": "Aggiorna i plugin", + "TaskRefreshPeopleDescription": "Aggiorna i metadati degli attori e registi nella tua libreria.", + "TaskRefreshPeople": "Aggiorna Persone", + "TaskCleanLogsDescription": "Rimuovi i file di log più vecchi di {0} giorni.", + "TaskCleanLogs": "Pulisci la cartella dei log", + "TaskRefreshLibraryDescription": "Scansiona la libreria alla ricerca di nuovi file e aggiorna i metadati.", + "TaskRefreshLibrary": "Scansione della libreria", + "TaskRefreshChapterImagesDescription": "Crea le miniature per i video che hanno capitoli.", + "TaskRefreshChapterImages": "Estrai immagini capitolo", + "TaskCleanCacheDescription": "Cancella i file di cache non più necessari al sistema.", + "TaskCleanCache": "Pulisci la cartella della cache", + "TasksChannelsCategory": "Canali su Internet", + "TasksApplicationCategory": "Applicazione", + "TasksLibraryCategory": "Libreria", + "TasksMaintenanceCategory": "Manutenzione", + "TaskCleanActivityLog": "Attività di Registro Completate", + "TaskCleanActivityLogDescription": "Elimina le voci del registro delle attività più vecchie dell'età configurata.", + "Undefined": "Non specificato", + "Forced": "Forzato", + "Default": "Predefinito", + "TaskOptimizeDatabaseDescription": "Compatta database e tronca spazi liberi. Eseguire questa azione dopo la scansione o dopo aver fatto altre modifiche inerenti il database potrebbe aumentarne le prestazioni.", + "TaskOptimizeDatabase": "Ottimizza database", + "TaskKeyframeExtractor": "Estrattore di Keyframe", + "TaskKeyframeExtractorDescription": "Estrae i keyframe dai video per creare migliori scalette HLS. Questa procedura potrebbe richiedere molto tempo.", + "External": "Esterno", + "HearingImpaired": "Non udenti", + "TaskRefreshTrickplayImages": "Genera immagini Trickplay", + "TaskRefreshTrickplayImagesDescription": "Crea anteprime trickplay per i video nelle librerie abilitate.", + "TaskCleanCollectionsAndPlaylists": "Ripulisci le collezioni e le scalette", + "TaskCleanCollectionsAndPlaylistsDescription": "Rimuove gli elementi dalle collezioni e dalle scalette che non esistono più.", + "TaskAudioNormalization": "Normalizzazione dell'audio", + "TaskAudioNormalizationDescription": "Scansiona i file alla ricerca dei dati per la normalizzazione dell'audio.", + "TaskDownloadMissingLyricsDescription": "Scarica testi per le canzoni", + "TaskDownloadMissingLyrics": "Scarica testi mancanti", + "TaskMoveTrickplayImages": "Sposta le immagini Trickplay", + "TaskMoveTrickplayImagesDescription": "Sposta le immagini Trickplay esistenti secondo la configurazione della libreria.", + "TaskExtractMediaSegmentsDescription": "Estrae o ottiene segmenti multimediali dai plugin abilitati MediaSegment.", + "TaskExtractMediaSegments": "Scansiona Segmento Media", + "CleanupUserDataTask": "Task di pulizia dei dati utente", + "CleanupUserDataTaskDescription": "Pulisce tutti i dati utente (stato di visione, status preferiti, ecc.) dai contenuti non più presenti da almeno 90 giorni." +} diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json new file mode 100644 index 00000000..d564d54c --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ja.json @@ -0,0 +1,141 @@ +{ + "Albums": "アルバム", + "AppDeviceValues": "アプリ: {0}, デバイス: {1}", + "Application": "アプリケーション", + "Artists": "アーティスト", + "AuthenticationSucceededWithUserName": "{0} 認証に成功しました", + "Books": "ブック", + "CameraImageUploadedFrom": "新しいカメライメージが {0}からアップロードされました", + "Channels": "チャンネル", + "ChapterNameValue": "チャプター {0}", + "Collections": "コレクション", + "DeviceOfflineWithName": "{0} が切断しました", + "DeviceOnlineWithName": "{0} が接続しました", + "FailedLoginAttemptWithUserName": "{0} からのログインに失敗しました", + "Favorites": "お気に入り", + "Folders": "フォルダー", + "Genres": "ジャンル", + "HeaderAlbumArtists": "アルバムアーティスト", + "HeaderContinueWatching": "再生を続ける", + "HeaderFavoriteAlbums": "お気に入りのアルバム", + "HeaderFavoriteArtists": "お気に入りのアーティスト", + "HeaderFavoriteEpisodes": "お気に入りのエピソード", + "HeaderFavoriteShows": "お気に入りの番組", + "HeaderFavoriteSongs": "お気に入りの曲", + "HeaderLiveTV": "ライブTV", + "HeaderNextUp": "次", + "HeaderRecordingGroups": "レコーディンググループ", + "HomeVideos": "ホームビデオ", + "Inherit": "継承", + "ItemAddedWithName": "{0} をライブラリーに追加しました", + "ItemRemovedWithName": "{0} をライブラリーから削除しました", + "LabelIpAddressValue": "IPアドレス: {0}", + "LabelRunningTimeValue": "時間: {0}", + "Latest": "最新", + "MessageApplicationUpdated": "Jellyfin Server を更新しました", + "MessageApplicationUpdatedTo": "Jellyfin Server を {0}に更新しました", + "MessageNamedServerConfigurationUpdatedWithValue": "サーバー設定項目の {0} を更新しました", + "MessageServerConfigurationUpdated": "サーバー設定を更新しました", + "MixedContent": "ミックスコンテンツ", + "Movies": "映画", + "Music": "音楽", + "MusicVideos": "ミュージックビデオ", + "NameInstallFailed": "{0}のインストールに失敗しました", + "NameSeasonNumber": "シーズン {0}", + "NameSeasonUnknown": "シーズン不明", + "NewVersionIsAvailable": "新しいバージョンの Jellyfin Server がダウンロード可能です。", + "NotificationOptionApplicationUpdateAvailable": "アプリケーションの更新があります", + "NotificationOptionApplicationUpdateInstalled": "アプリケーションは最新です", + "NotificationOptionAudioPlayback": "オーディオの再生を開始", + "NotificationOptionAudioPlaybackStopped": "オーディオの再生をストップしました", + "NotificationOptionCameraImageUploaded": "カメライメージがアップロードされました", + "NotificationOptionInstallationFailed": "インストール失敗", + "NotificationOptionNewLibraryContent": "新しいコンテンツを追加しました", + "NotificationOptionPluginError": "プラグインに障害が発生しました", + "NotificationOptionPluginInstalled": "プラグインがインストールされました", + "NotificationOptionPluginUninstalled": "プラグインがアンインストールされました", + "NotificationOptionPluginUpdateInstalled": "プラグインのアップデートをインストールしました", + "NotificationOptionServerRestartRequired": "サーバーを再起動してください", + "NotificationOptionTaskFailed": "スケジュールされていたタスクの失敗", + "NotificationOptionUserLockedOut": "ユーザーはロックされています", + "NotificationOptionVideoPlayback": "ビデオの再生を開始しました", + "NotificationOptionVideoPlaybackStopped": "ビデオを停止しました", + "Photos": "フォト", + "Playlists": "プレイリスト", + "Plugin": "プラグイン", + "PluginInstalledWithName": "{0} がインストールされました", + "PluginUninstalledWithName": "{0} がアンインストールされました", + "PluginUpdatedWithName": "{0} が更新されました", + "ProviderValue": "プロバイダ: {0}", + "ScheduledTaskFailedWithName": "{0} が失敗しました", + "ScheduledTaskStartedWithName": "{0} が開始されました", + "ServerNameNeedsToBeRestarted": "{0} を再起動してください", + "Shows": "番組", + "Songs": "曲", + "StartupEmbyServerIsLoading": "Jellyfin Server は現在読み込み中です。しばらくしてからもう一度お試しください。", + "SubtitleDownloadFailureFromForItem": "{0} から {1}の字幕のダウンロードに失敗しました", + "Sync": "同期", + "System": "システム", + "TvShows": "テレビ番組", + "User": "ユーザー", + "UserCreatedWithName": "ユーザー {0} が作成されました", + "UserDeletedWithName": "User {0} を削除しました", + "UserDownloadingItemWithValues": "{0} が {1} をダウンロードしています", + "UserLockedOutWithName": "ユーザー {0} はロックされています", + "UserOfflineFromDevice": "{0} は {1} から切断しました", + "UserOnlineFromDevice": "{0} は {1} からオンラインになりました", + "UserPasswordChangedWithName": "ユーザー {0} のパスワードは変更されました", + "UserPolicyUpdatedWithName": "ユーザーポリシーが{0}に更新されました", + "UserStartedPlayingItemWithValues": "{0} は {2}で{1} を再生しています", + "UserStoppedPlayingItemWithValues": "{0} は{2}で{1} の再生が終わりました", + "ValueHasBeenAddedToLibrary": "{0} をメディアライブラリーに追加しました", + "ValueSpecialEpisodeName": "スペシャル - {0}", + "VersionNumber": "バージョン {0}", + "TaskCleanLogsDescription": "{0} 日以上前のログを消去します。", + "TaskCleanLogs": "ログの掃除", + "TaskRefreshLibraryDescription": "メディアライブラリーをスキャンして、新しいファイルを探し、メタデータを更新します。", + "TaskRefreshLibrary": "メディアライブラリーをスキャン", + "TaskCleanCacheDescription": "不要なキャッシュを消去します。", + "TaskCleanCache": "キャッシュを消去", + "TasksChannelsCategory": "ネットチャンネル", + "TasksApplicationCategory": "アプリケーション", + "TasksLibraryCategory": "ライブラリー", + "TasksMaintenanceCategory": "メンテナンス", + "TaskRefreshChannelsDescription": "ネットチャンネルの情報を更新する。", + "TaskRefreshChannels": "チャンネルの更新", + "TaskCleanTranscodeDescription": "1日以上経過したトランスコードファイルを削除します。", + "TaskCleanTranscode": "トランスコードディレクトリの削除", + "TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。", + "TaskUpdatePlugins": "プラグインの更新", + "TaskRefreshPeopleDescription": "メディアライブラリー内の俳優や監督のメタデータを更新します。", + "TaskRefreshPeople": "俳優や監督のデータの更新", + "TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索する。", + "TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。", + "TaskRefreshChapterImages": "チャプター画像を抽出する", + "TaskDownloadMissingSubtitles": "不足している字幕をダウンロードする", + "TaskCleanActivityLogDescription": "設定された期間よりも古いアクティビティの履歴を削除します。", + "TaskCleanActivityLog": "アクティビティの履歴を消去", + "Undefined": "未定義", + "Forced": "強制", + "Default": "デフォルト", + "TaskOptimizeDatabaseDescription": "データベースをコンパクトにして、空き領域を切り詰めます。メディアライブラリーのスキャンやその他のデータベースの更新を伴う変更の後でこのタスクを実行すると、パフォーマンスが向上します。", + "TaskOptimizeDatabase": "データベースの最適化", + "TaskKeyframeExtractorDescription": "より正確なHLSプレイリストを作成するため、動画ファイルからキーフレームを抽出する。この処理には時間がかかる場合があります。", + "TaskKeyframeExtractor": "キーフレーム抽出", + "External": "外部", + "HearingImpaired": "聴覚障害の方", + "TaskRefreshTrickplayImages": "トリックプレー画像を生成", + "TaskRefreshTrickplayImagesDescription": "有効なライブラリ内のビデオをもとにトリックプレーのプレビューを生成します。", + "TaskCleanCollectionsAndPlaylists": "コレクションとプレイリストをクリーンアップ", + "TaskAudioNormalization": "音声の正規化", + "TaskAudioNormalizationDescription": "音声の正規化データのためにファイルをスキャンします。", + "TaskCleanCollectionsAndPlaylistsDescription": "在しなくなったコレクションやプレイリストからアイテムを削除します。", + "TaskDownloadMissingLyricsDescription": "歌詞をダウンロード", + "TaskExtractMediaSegments": "メディアセグメントを読み取る", + "TaskMoveTrickplayImages": "Trickplayの画像を移動", + "TaskMoveTrickplayImagesDescription": "ライブラリ設定によりTrickplayのファイルを移動。", + "TaskDownloadMissingLyrics": "失われた歌詞をダウンロード", + "TaskExtractMediaSegmentsDescription": "MediaSegment 対応プラグインからメディア セグメントを抽出または取得します。", + "CleanupUserDataTask": "ユーザーデータのクリーンアップタスク", + "CleanupUserDataTaskDescription": "90日以上存在しないメディアに対して、視聴状態やお気に入り状態などのユーザーデータをすべて削除します。" +} diff --git a/Emby.Server.Implementations/Localization/Core/jbo.json b/Emby.Server.Implementations/Localization/Core/jbo.json new file mode 100644 index 00000000..1b47bb2f --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/jbo.json @@ -0,0 +1,7 @@ +{ + "Albums": "lo albuma", + "Artists": "lo larpra", + "Books": "lo cukta", + "HeaderAlbumArtists": "lo albuma larpra", + "Playlists": "lo zgipor" +} diff --git a/Emby.Server.Implementations/Localization/Core/ka.json b/Emby.Server.Implementations/Localization/Core/ka.json new file mode 100644 index 00000000..2d02522f --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ka.json @@ -0,0 +1,129 @@ +{ + "Genres": "ჟანრები", + "HeaderAlbumArtists": "ალბომის შემსრულებლები", + "HeaderFavoriteAlbums": "რჩეული ალბომები", + "TasksApplicationCategory": "აპლიკაცია", + "Albums": "ალბომები", + "AppDeviceValues": "აპლიკაცია: {0}, მოწყობილობა: {1}", + "Application": "აპლიკაცია", + "Artists": "არტისტი", + "AuthenticationSucceededWithUserName": "{0} -ის ავთენტიკაცია წარმატებულია", + "Books": "წიგნები", + "Forced": "ძალით", + "Inherit": "მემკვიდრეობით", + "Latest": "უახლესი", + "Movies": "ფილმები", + "Music": "მუსიკა", + "Photos": "ფოტოები", + "Playlists": "დასაკრავი სიები", + "Plugin": "დამატება", + "Shows": "სერიალები", + "Songs": "სიმღერები", + "Sync": "სინქრონიზაცია", + "System": "სისტემა", + "Undefined": "აღუწერელი", + "User": "მომხმარებელი", + "TasksMaintenanceCategory": "რემონტი", + "TasksLibraryCategory": "ბიბლიოთეკა", + "ChapterNameValue": "თავი {0}", + "HeaderContinueWatching": "ყურების გაგრძელება", + "HeaderFavoriteArtists": "რჩეული შემსრულებლები", + "DeviceOfflineWithName": "{0} გაითიშა", + "External": "გარე", + "HeaderFavoriteEpisodes": "რჩეული ეპიზოდები", + "HeaderFavoriteSongs": "რჩეული სიმღერები", + "HeaderRecordingGroups": "ჩამწერი ჯგუფები", + "HearingImpaired": "სმენადაქვეითებული", + "LabelRunningTimeValue": "გაშვებულობის დრო: {0}", + "MessageApplicationUpdatedTo": "Jellyfin-ის სერვერი განახლდა {0}-ზე", + "MessageNamedServerConfigurationUpdatedWithValue": "სერვერის კონფიგურაციის სექცია {0} განახლდა", + "MixedContent": "შერეული შემცველობა", + "MusicVideos": "მუსიკის ვიდეოები", + "NotificationOptionInstallationFailed": "დაყენების შეცდომა", + "NotificationOptionApplicationUpdateInstalled": "აპლიკაციის განახლება დაყენებულია", + "NotificationOptionAudioPlayback": "აუდიოს დაკვრა დაწყებულია", + "NotificationOptionCameraImageUploaded": "კამერის გამოსახულება ატვირთულია", + "NotificationOptionVideoPlaybackStopped": "ვიდეოს დაკვრა გაჩერებულია", + "PluginUninstalledWithName": "{0} წაიშალა", + "ScheduledTaskStartedWithName": "{0} გაეშვა", + "VersionNumber": "ვერსია {0}", + "TasksChannelsCategory": "ინტერნეტ-არხები", + "ValueSpecialEpisodeName": "სპეციალური - {0}", + "TaskRefreshChannelsDescription": "ინტერნეტ-არხის ინფორმაციის განახლება.", + "Channels": "არხები", + "Collections": "კოლექციები", + "Default": "ნაგულისხმები", + "Favorites": "რჩეულები", + "Folders": "საქაღალდეები", + "HeaderFavoriteShows": "რჩეული სერიალები", + "HeaderLiveTV": "ცოცხალი TV", + "HeaderNextUp": "შემდეგი ზემოთ", + "HomeVideos": "სახლის ვიდეოები", + "NameSeasonNumber": "სეზონი {0}", + "NameSeasonUnknown": "სეზონი უცნობია", + "NotificationOptionPluginError": "დამატების შეცდომა", + "NotificationOptionPluginInstalled": "დამატება დაყენებულია", + "NotificationOptionPluginUninstalled": "დამატება წაიშალა", + "ProviderValue": "მომწოდებელი: {0}", + "ScheduledTaskFailedWithName": "{0} ავარიულია", + "TvShows": "TV სერიალები", + "TaskRefreshPeople": "ხალხის განახლება", + "TaskUpdatePlugins": "დამატებების განახლება", + "TaskRefreshChannels": "არხების განახლება", + "TaskOptimizeDatabase": "ბაზების ოპტიმიზაცია", + "TaskKeyframeExtractor": "საკვანძო კადრის გამომღები", + "DeviceOnlineWithName": "{0} შეერთებულია", + "LabelIpAddressValue": "IP მისამართი: {0}", + "NameInstallFailed": "{0}-ის დაყენების შეცდომა", + "NotificationOptionApplicationUpdateAvailable": "ხელმისაწვდომია აპლიკაციის განახლება", + "NotificationOptionAudioPlaybackStopped": "აუდიოს დაკვრა გაჩერებულია", + "NotificationOptionNewLibraryContent": "ახალი შემცველობა დამატებულია", + "NotificationOptionPluginUpdateInstalled": "დამატების განახლება დაყენებულია", + "NotificationOptionServerRestartRequired": "სერვერის გადატვირთვა აუცილებელია", + "NotificationOptionTaskFailed": "დაგეგმილი ამოცანის შეცდომა", + "NotificationOptionUserLockedOut": "მომხმარებელი დაიბლოკა", + "NotificationOptionVideoPlayback": "ვიდეოს დაკვრა დაწყებულია", + "PluginInstalledWithName": "{0} დაყენებულია", + "PluginUpdatedWithName": "{0} განახლდა", + "TaskCleanActivityLog": "აქტივობების ჟურნალის გასუფთავება", + "TaskCleanCache": "ქეშის საქაღალდის გასუფთავება", + "TaskRefreshChapterImages": "თავის სურათების გაშლა", + "TaskRefreshLibrary": "მედიის ბიბლიოთეკის სკანირება", + "TaskCleanLogs": "ჟურნალის საქაღალდის გასუფთავება", + "TaskCleanTranscode": "ტრანსკოდირების საქაღალდის გასუფთავება", + "TaskDownloadMissingSubtitles": "ნაკლული სუბტიტრების გადმოწერა", + "UserDownloadingItemWithValues": "{0} -ი {0}-ს იწერს", + "FailedLoginAttemptWithUserName": "{0}-დან შემოსვლის შეცდომა", + "MessageApplicationUpdated": "Jellyfin-ის სერვერი განახლდა", + "MessageServerConfigurationUpdated": "სერვერის კონფიგურაცია განახლდა", + "ServerNameNeedsToBeRestarted": "საჭიროა {0}-ის გადატვირთვა", + "UserCreatedWithName": "მომხმარებელი {0} შეიქმნა", + "UserDeletedWithName": "მომხმარებელი {0} წაშლილია", + "UserOnlineFromDevice": "{0}-ი ხაზზეა {1}-დან", + "UserOfflineFromDevice": "{0}-ი {1}-დან გაითიშა", + "ItemAddedWithName": "{0} ჩამატებულია ბიბლიოთეკაში", + "ItemRemovedWithName": "{0} წაშლილია ბიბლიოთეკიდან", + "UserLockedOutWithName": "მომხმარებელი {0} დაბლოკილია", + "UserStartedPlayingItemWithValues": "{0} თამაშობს {1}-ს {2}-ზე", + "UserPasswordChangedWithName": "მომხმარებლისთვის {0} პაროლი შეცვლილია", + "UserPolicyUpdatedWithName": "{0}-ის მომხმარებლის პოლიტიკა განახლდა", + "UserStoppedPlayingItemWithValues": "{0}-მა დაამთავრა {1}-ის დაკვრა {2}-ზე", + "TaskRefreshChapterImagesDescription": "თავების მქონე ვიდეოებისთვის მინიატურების შექმნა.", + "TaskKeyframeExtractorDescription": "უფრო ზუსტი HLS დასაკრავი სიებისითვის ვიდეოდან საკვანძო გადრების ამოღება. შეიძლება საკმაო დრო დასჭირდეს.", + "NewVersionIsAvailable": "გადმოსაწერად ხელმისაწვდომია Jellyfin -ის ახალი ვერსია.", + "CameraImageUploadedFrom": "ახალი კამერის გამოსახულება ატვირთულია {0}-დან", + "StartupEmbyServerIsLoading": "Jellyfin სერვერი იტვირთება. მოგვიანებით სცადეთ.", + "SubtitleDownloadFailureFromForItem": "{0}-დან {1}-სთვის სუბტიტრების გადმოწერის შეცდომა", + "ValueHasBeenAddedToLibrary": "{0} დაემატა თქვენს მედიის ბიბლიოთეკას", + "TaskCleanActivityLogDescription": "მითითებულ ასაკზე ძველი ჟურნალის ჩანაწერების წაშლა.", + "TaskCleanCacheDescription": "სისტემისთვის არასაჭირო ქეშის ფაილების წაშლა.", + "TaskRefreshLibraryDescription": "თქვენი მედია ბიბლიოთეკაში ახალი ფაილების ძებნა და მეტამონაცემების განახლება.", + "TaskCleanLogsDescription": "{0} დღეზე ძველი ჟურნალის ფაილების წაშლა.", + "TaskRefreshPeopleDescription": "თქვენს მედიის ბიბლიოთეკაში მსახიობების და რეჟისორების მეტამონაცემების განახლება.", + "TaskUpdatePluginsDescription": "ავტომატურად განახლებადად მონიშნული დამატებების განახლებების გადმოწერა და დაყენება.", + "TaskCleanTranscodeDescription": "ერთ დღეზე უფრო ძველი ტრანსკოდირების ფაილების წაშლა.", + "TaskDownloadMissingSubtitlesDescription": "მეტამონაცემებზე დაყრდნობით ინტერნეტში ნაკლული სუბტიტრების ძებნა.", + "TaskOptimizeDatabaseDescription": "ბაზს შეკუშვა და ადგილის გათავისუფლება. ამ ამოცანის ბიბლიოთეკის სკანირების ან ნებისმიერი ცვლილების, რომელიც ბაზაში რამეს აკეთებს, გაშვებას შეუძლია ბაზის წარმადობა გაზარდოს.", + "TaskRefreshTrickplayImagesDescription": "ქმნის trickplay წინასწარ ხედებს ვიდეოებისთვის ჩართულ ბიბლიოთეკებში.", + "TaskRefreshTrickplayImages": "Trickplay სურათების გენერირება" +} diff --git a/Emby.Server.Implementations/Localization/Core/kab.json b/Emby.Server.Implementations/Localization/Core/kab.json new file mode 100644 index 00000000..9551f0e5 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/kab.json @@ -0,0 +1,14 @@ +{ + "Music": "Aẓawan", + "Sync": "Amtawi", + "Photos": "Tiwlafin", + "Movies": "Isura", + "External": "Azɣaray", + "User": "Aseqdac", + "Folders": "Ikaramen", + "Favorites": "Ismenyifen", + "Default": "Lexṣas", + "Collections": "Tigrummiwin", + "Channels": "Ibuda", + "Albums": "Iseɣraz" +} diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json new file mode 100644 index 00000000..fc5fcf3c --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/kk.json @@ -0,0 +1,129 @@ +{ + "Albums": "Älbomdar", + "AppDeviceValues": "Qoldanba: {0}, Qūrylğy: {1}", + "Application": "Qoldanba", + "Artists": "Oryndauşylar", + "AuthenticationSucceededWithUserName": "{0} tüpnūsqalyq rastaluy sättı aiaqtaldy", + "Books": "Kıtaptar", + "CameraImageUploadedFrom": "{0} kamerasynan jaña suret jüktep salyndy", + "Channels": "Arnalar", + "ChapterNameValue": "{0}-sahna", + "Collections": "Jiyntyqtar", + "DeviceOfflineWithName": "{0} ajyratylğan", + "DeviceOnlineWithName": "{0} qosylğan", + "FailedLoginAttemptWithUserName": "{0} tarapynan kıru äreketı sätsız aiaqtaldy", + "Favorites": "Tañdaulylar", + "Folders": "Qaltalar", + "Genres": "Janrlar", + "HeaderAlbumArtists": "Älbom oryndauşylary", + "HeaderContinueWatching": "Qaraudy jalğastyru", + "HeaderFavoriteAlbums": "Tañdauly älbomdar", + "HeaderFavoriteArtists": "Tañdauly oryndauşylar", + "HeaderFavoriteEpisodes": "Tañdauly telebölımder", + "HeaderFavoriteShows": "Tañdauly körsetımder", + "HeaderFavoriteSongs": "Tañdauly äuender", + "HeaderLiveTV": "Efir", + "HeaderNextUp": "Kezektı", + "HeaderRecordingGroups": "Jazba toptary", + "HomeVideos": "Üilık beineler", + "Inherit": "İelenu", + "ItemAddedWithName": "{0} tasyğyşhanağa üstelindı", + "ItemRemovedWithName": "{0} tasyğyşhanadan alastaldy", + "LabelIpAddressValue": "IP-mekenjaiy: {0}", + "LabelRunningTimeValue": "Oinatu uaqyty: {0}", + "Latest": "Eñ keiıngı", + "MessageApplicationUpdated": "Jellyfin Serverı jañartyldy", + "MessageApplicationUpdatedTo": "Jellyfin Serverı {0} nūsqasyna jañartyldy", + "MessageNamedServerConfigurationUpdatedWithValue": "Server teñşelımderınıñ {0} bölımı jañartyldy", + "MessageServerConfigurationUpdated": "Server teñşelımderı jañartyldy", + "MixedContent": "Aralas mazmūn", + "Movies": "Filmder", + "Music": "Muzyka", + "MusicVideos": "Muzykalyq beineler", + "NameInstallFailed": "{0} ornatyluy sätsız", + "NameSeasonNumber": "{0}-mausym", + "NameSeasonUnknown": "Belgısız mausym", + "NewVersionIsAvailable": "Jaña Jellyfin Server nūsqasy jüktep aluğa qoljetımdı.", + "NotificationOptionApplicationUpdateAvailable": "Qoldanba jañartuy qoljetımdı", + "NotificationOptionApplicationUpdateInstalled": "Qoldanba jañartuy ornatyldy", + "NotificationOptionAudioPlayback": "Dybys oinatuy bastaldy", + "NotificationOptionAudioPlaybackStopped": "Dybys oinatuy toqtatyldy", + "NotificationOptionCameraImageUploaded": "Kameradan fotosuret jüktep salynğan", + "NotificationOptionInstallationFailed": "Ornatu sätsızdıgı", + "NotificationOptionNewLibraryContent": "Jaña mazmūn üstelıngen", + "NotificationOptionPluginError": "Plagin sätsızdıgı", + "NotificationOptionPluginInstalled": "Plagin ornatyldy", + "NotificationOptionPluginUninstalled": "Plagin ornatuy boldyrylmady", + "NotificationOptionPluginUpdateInstalled": "Plagin jañartuy ornatyldy", + "NotificationOptionServerRestartRequired": "Serverdı qaita ıske qosu qajet", + "NotificationOptionTaskFailed": "Josparlağan tapsyrma sätsızdıgı", + "NotificationOptionUserLockedOut": "Paidalanuşy qūrsauly", + "NotificationOptionVideoPlayback": "Beine oinatuy bastaldy", + "NotificationOptionVideoPlaybackStopped": "Beine oinatuy toqtatyldy", + "Photos": "Fotosuretter", + "Playlists": "Oinatu tızımderı", + "Plugin": "Plagin", + "PluginInstalledWithName": "{0} ornatyldy", + "PluginUninstalledWithName": "{0} joiyldy", + "PluginUpdatedWithName": "{0} jañartyldy", + "ProviderValue": "Jetkızuşı: {0}", + "ScheduledTaskFailedWithName": "{0} sätsız", + "ScheduledTaskStartedWithName": "{0} ıske qosyldy", + "ServerNameNeedsToBeRestarted": "{0} qaita ıske qosu qajet", + "Shows": "Körsetımder", + "Songs": "Äuender", + "StartupEmbyServerIsLoading": "Jellyfin Server jüktelude. Ärekettı köp ūzamai qaitalañyz.", + "SubtitleDownloadFailureFromForItem": "{1} üşın subtitrlerdı {0} közınen jüktep alu sätsız", + "Sync": "Ündestıru", + "System": "Jüie", + "TvShows": "TD-körsetımder", + "User": "Paidalanuşy", + "UserCreatedWithName": "Paidalanuşy {0} jasalğan", + "UserDeletedWithName": "Paidalanuşy {0} joiylğan", + "UserDownloadingItemWithValues": "{0} — {1} jüktep aluda", + "UserLockedOutWithName": "Paidalanuşy {0} qūrsaulanğan", + "UserOfflineFromDevice": "{0} — {1} tarapynan ajyratyldy", + "UserOnlineFromDevice": "{0} — {1} tarapynan qosyldy", + "UserPasswordChangedWithName": "Paidalanuşy {0} üşın paröl özgertıldı", + "UserPolicyUpdatedWithName": "Paidalanuşy {0} üşın saiasattary jañartyldy", + "UserStartedPlayingItemWithValues": "{0} — {2} tarapynan {1} oinatuda", + "UserStoppedPlayingItemWithValues": "{0} — {2} tarapynan {1} oinatuyn toqtatty", + "ValueHasBeenAddedToLibrary": "{0} tasyğyşhanağa üstelındı", + "ValueSpecialEpisodeName": "Arnaiy - {0}", + "VersionNumber": "Nūsqasy {0}", + "Default": "Ädepkı", + "TaskDownloadMissingSubtitles": "Joq subtitrlerdı jüktep alu", + "TaskRefreshChannels": "Arnalardy jañğyrtu", + "TaskCleanTranscode": "Qaita kodtau katalogyn tazalau", + "TaskUpdatePlugins": "Plaginderdı jañartu", + "TaskRefreshPeople": "Adamdardy jañğyrtu", + "TaskCleanLogs": "Jūrnal katalogyn tazalau", + "TaskRefreshLibrary": "Tasyğyşhanany skanerleu", + "TaskRefreshChapterImages": "Sahna suretterın şyğaryp alu", + "TaskCleanCache": "Keş katalogyn tazalau", + "TaskCleanActivityLog": "Äreket jūrnalyn tazalau", + "TasksChannelsCategory": "Internet-arnalar", + "TasksApplicationCategory": "Qoldanba", + "TasksLibraryCategory": "Tasyğyşhana", + "TasksMaintenanceCategory": "Qyzmet körsetu", + "Undefined": "Anyqtalmağan", + "Forced": "Mäjbürlı", + "TaskDownloadMissingSubtitlesDescription": "Metaderekter teñşelımderı negızınde joq subtitrlerdı İnternetten ızdeidı.", + "TaskRefreshChannelsDescription": "Internet-arnalar mälımetterın jañğyrtady.", + "TaskCleanTranscodeDescription": "Bіr künnen asqan qaita kodtau faildaryn joiady.", + "TaskUpdatePluginsDescription": "Avtomatty türde jañartuğa teñşelgen plaginder üşın jañartulardy jüktep alady jäne ornatady.", + "TaskRefreshPeopleDescription": "Tasyğyşhanadağy aktörler men rejisörler metaderekterın jañartady.", + "TaskCleanLogsDescription": "{0} künnen asqan jūrnal faildaryn joiady.", + "TaskRefreshLibraryDescription": "Tasyğyşhanadağy jaña faildardy skanerleidі jäne metaderekterdı jañğyrtady.", + "TaskRefreshChapterImagesDescription": "Sahnalary bar beineler üşın nobailar jasaidy.", + "TaskCleanCacheDescription": "Jüiede qajet emes keştelgen faildardy joiady.", + "TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teñşelgen jasynan asqan jazbalary joiady.", + "TaskOptimizeDatabaseDescription": "Derekqordy qysyp, bos oryndy qysqartady. Būl tapsyrmany tasyğyşhanany skanerlegennen keiın nemese derekqorğa meñzeitın basqa özgertuler ıstelgennen keiın oryndau önımdılıktı damytuy mümkın.", + "TaskOptimizeDatabase": "Derekqordy oñtailandyru", + "TaskKeyframeExtractorDescription": "Naqtyraq HLS oynatu tızımderın jasau üşın beinefaildardan negızgı kadrlardy şyğarady. Būl tapsyrma ūzaq uaqytqa sozyluy mümkın.", + "TaskKeyframeExtractor": "Negızgı kadrlardy şyğaru", + "External": "Syrtqy", + "TaskRefreshTrickplayImagesDescription": "Іске қосылған кітапханалардағы бейнелер үшін Trickplay алдын ала түрінде көрсетілімді жасайды.", + "TaskRefreshTrickplayImages": "Trickplay үшін суреттерді жасау", + "HearingImpaired": "Есту қабілеті нашарға" +} diff --git a/Emby.Server.Implementations/Localization/Core/km.json b/Emby.Server.Implementations/Localization/Core/km.json new file mode 100644 index 00000000..5d10975f --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/km.json @@ -0,0 +1,133 @@ +{ + "Albums": "អាលប៊ុម", + "MessageApplicationUpdatedTo": "ម៉ាស៊ីនមេនៃJellyfinត្រូវបានអាប់ដេតទៅកាន់ {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "ការកំណត់ម៉ាស៊ីនមេ ផ្នែក {0} ត្រូវបានអាប់ដេត", + "MessageServerConfigurationUpdated": "ការកំណត់ម៉ាស៊ីនមេត្រូវបានអាប់ដេត", + "AppDeviceValues": "កម្មវិធី: {0}, ឧបករណ៍: {1}", + "MixedContent": "មាតិកាចម្រុះ", + "UserLockedOutWithName": "អ្នកប្រើប្រាស់ {0} ត្រូវ​បាន​ផ្អាក", + "Application": "កម្មវិធី", + "Artists": "សិល្បករ", + "AuthenticationSucceededWithUserName": "{0} បានផ្ទៀងផ្ទាត់ដោយជោគជ័យ", + "Books": "សៀវភៅ", + "NameSeasonNumber": "រដូវកាលទី {0}", + "NotificationOptionPluginInstalled": "Plugin បានដំឡើងរួច", + "CameraImageUploadedFrom": "រូបភាពកាមេរ៉ាថ្មីត្រូវបានបង្ហោះពី {0}", + "Channels": "ប៉ុស្ត៍", + "ChapterNameValue": "ជំពូក {0}", + "Collections": "បណ្តុំ", + "External": "ខាងក្រៅ", + "Default": "លំនាំដើម", + "NotificationOptionInstallationFailed": "ការដំឡើងមិនបានសម្រេច", + "DeviceOfflineWithName": "{0} បានផ្តាច់", + "Folders": "ថតឯកសារ", + "DeviceOnlineWithName": "{0} បានភ្ចាប់", + "HearingImpaired": "ខ្សោយការស្តាប់", + "HomeVideos": "វីឌីអូថតខ្លួនឯង", + "Favorites": "ចំណូលចិត្ត", + "HeaderFavoriteEpisodes": "ភាគដែលចូលចិត្ត", + "Forced": "បង្ខំ", + "Genres": "ប្រភេទ", + "HeaderFavoriteArtists": "សិល្បករដែលចូលចិត្ត", + "NotificationOptionApplicationUpdateAvailable": "កម្មវិធី យើងអាចអាប់ដេតបាន", + "NotificationOptionApplicationUpdateInstalled": "កម្មវិធី ដែលបានដំឡើងរួច", + "NotificationOptionAudioPlaybackStopped": "ការ​ចាក់សម្លេងបានផ្អាក", + "HeaderContinueWatching": "បន្តមើល", + "HeaderFavoriteAlbums": "អាល់ប៊ុមដែលចូលចិត្ត", + "HeaderFavoriteShows": "រឿងភាគដែលចូលចិត្ត", + "NewVersionIsAvailable": "មានជំនាន់ថ្មី ម៉ាស៊ីនមេJellyfin អាចទាញយកបាន.", + "HeaderAlbumArtists": "សិល្បករអាល់ប៊ុម", + "NotificationOptionCameraImageUploaded": "រូបភាពពីកាំមេរ៉ាបានអាប់ឡូតរួច", + "HeaderFavoriteSongs": "ចម្រៀងដែលចូលចិត្ត", + "HeaderNextUp": "បន្ទាប់", + "HeaderLiveTV": "ទូរទស្សន៍ផ្សាយផ្ទាល់", + "Movies": "រឿង", + "HeaderRecordingGroups": "ក្រុមនៃការថត", + "Music": "តន្ត្រី", + "Inherit": "មរតក", + "MusicVideos": "វីដេអូតន្ត្រី", + "NameInstallFailed": "{0} ការដំឡើងបានបរាជ័យ", + "NotificationOptionNewLibraryContent": "មាតិកាថ្មីៗត្រូវបានបន្ថែម", + "ItemAddedWithName": "{0} ត្រូវបានបន្ថែមទៅបណ្ណាល័យ", + "NameSeasonUnknown": "រដូវកាលមិនច្បាស់លាស់", + "ItemRemovedWithName": "{0} ត្រូវបានដកចេញពីបណ្ណាល័យ", + "LabelIpAddressValue": "លេខ IP: {0}", + "LabelRunningTimeValue": "ពេលវេលាកំពុងដំណើរការ: {0}", + "Latest": "ចុងក្រោយ", + "NotificationOptionAudioPlayback": "ការ​ចាក់​សំឡេង​បាន​ចាប់ផ្ដើម", + "NotificationOptionPluginError": "Plugin មិនដំណើរការ", + "NotificationOptionPluginUninstalled": "Plugin បានលុបចេញរួច", + "MessageApplicationUpdated": "ម៉ាស៊ីនមេនៃJellyfinត្រូវបានអាប់ដេត", + "NotificationOptionPluginUpdateInstalled": "Plugin អាប់ដេតបានដំឡើងរួច", + "NotificationOptionUserLockedOut": "អ្នកប្រើប្រាស់ត្រូវបានជាប់គាំង", + "NotificationOptionServerRestartRequired": "តម្រូវឱ្យចាប់ផ្ដើមម៉ាស៊ីនមេឡើងវិញ", + "Photos": "រូបថត", + "Playlists": "បញ្ជីចាក់", + "Plugin": "Plugin", + "PluginInstalledWithName": "{0} ត្រូវបានដំឡើង", + "NotificationOptionTaskFailed": "កិច្ចការដែលបានគ្រោងទុកបានបរាជ័យ", + "PluginUpdatedWithName": "{0} ត្រូវបានអាប់ដេត", + "NotificationOptionVideoPlayback": "ការចាក់វីដេអូបានចាប់ផ្តើម", + "Songs": "ចម្រៀង", + "ScheduledTaskStartedWithName": "{0} បានចាប់ផ្តើម", + "NotificationOptionVideoPlaybackStopped": "ការ​ចាក់​វីដេអូ​បាន​បញ្ឈប់", + "PluginUninstalledWithName": "{0} ត្រូវបានលុបចេញ", + "Shows": "រឿងភាគ", + "ProviderValue": "អ្នកផ្តល់សេវា: {0}", + "SubtitleDownloadFailureFromForItem": "សាប់ថាយថលបានបរាជ័យក្នុងការទាញយកពី {0} នៃ {1}", + "Sync": "ធ្វើអោយដំណាលគ្នា", + "System": "ប្រព័ន្ធ", + "TvShows": "កម្មវិធីទូរទស្សន៍", + "ScheduledTaskFailedWithName": "{0} បានបរាជ័យ", + "Undefined": "មិនបានកំណត់", + "User": "អ្នកប្រើប្រាស់", + "UserCreatedWithName": "អ្នកប្រើប្រាស់ {0} ត្រូវបានបង្កើតឡើង", + "ServerNameNeedsToBeRestarted": "{0} ចាំបាច់ត្រូវចាប់ផ្តើមឡើងវិញ", + "StartupEmbyServerIsLoading": "ម៉ាស៊ីនមេJellyfin កំពុងដំណើរការ. សូមព្យាយាមម្តងទៀតក្នុងពេលឆាប់ៗនេះ.", + "UserDeletedWithName": "អ្នកប្រើប្រាស់ {0} ត្រូវបានលុបចេញ", + "UserOnlineFromDevice": "{0} បានឃើញអនឡានពី {1}", + "UserDownloadingItemWithValues": "{0} កំពុងទាញយក {1}", + "UserOfflineFromDevice": "{0} បានផ្តាច់ចេញពី {1}", + "UserStartedPlayingItemWithValues": "{0} កំពុងចាក់ {1} នៅលើ {2}", + "TaskRefreshChapterImagesDescription": "បង្កើតរូបភាពតូចៗសម្រាប់វីដេអូដែលមានតាមជំពូក.", + "VersionNumber": "កំណែ {0}", + "TasksMaintenanceCategory": "តំហែរទាំ", + "TasksLibraryCategory": "បណ្ណាល័យ", + "TasksApplicationCategory": "កម្មវិធី", + "TaskCleanActivityLog": "សម្អាតកំណត់ហេតុសកម្មភាព", + "UserPasswordChangedWithName": "ពាក្យសម្ងាត់ត្រូវបានផ្លាស់ប្តូរសម្រាប់អ្នកប្រើប្រាស់ {0}", + "TaskCleanCache": "សម្អាតបញ្ជីឃ្លាំងសម្ងាត់", + "TaskRefreshChapterImages": "ដកស្រង់រូបភាពតាមជំពូក", + "UserPolicyUpdatedWithName": "គោលការណ៍អ្នកប្រើប្រាស់ត្រូវបានធ្វើបច្ចុប្បន្នភាពសម្រាប់ {0}", + "UserStoppedPlayingItemWithValues": "{0} បានបញ្ចប់ការចាក់ {1} នៅលើ {2}", + "ValueHasBeenAddedToLibrary": "{0} ត្រូវបានបញ្ចូលទៅក្នុងបណ្ណាល័យរឿងរបស់អ្នក", + "ValueSpecialEpisodeName": "ពិសេស - {0}", + "TasksChannelsCategory": "ប៉ុស្តតាមអ៊ីនធឺណិត", + "TaskAudioNormalization": "ធ្វើឱ្យមានតន្ត្រីមានសម្លេងស្មើគ្នា", + "TaskCleanActivityLogDescription": "លុបកំណត់ហេតុសកម្មភាពចាស់ជាងអាយុដែលបានកំណត់រចនាសម្ព័ន្ធ.", + "TaskCleanCacheDescription": "លុបឯកសារឃ្លាំងសម្ងាត់លែងត្រូវការដោយប្រព័ន្ធ.", + "TaskRefreshLibraryDescription": "ស្កេនបណ្ណាល័យរឿងរបស់អ្នក សម្រាប់ឯកសារថ្មីៗ និងmetadata ឡើងវិញ.", + "TaskCleanLogsDescription": "លុបឯកសារកំណត់ហេតុដែលមានអាយុកាលលើសពី {0} ថ្ងៃ.", + "TaskRefreshPeopleDescription": "ធ្វើបច្ចុប្បន្នភាព metadata សម្រាប់តួសម្តែង និងអ្នកដឹកនាំនៅក្នុងបណ្ណាល័យរឿងរបស់អ្នក.", + "TaskOptimizeDatabaseDescription": "បង្រួម Database និង Truncate free space. ដំណើរការកិច្ចការនេះ បន្ទាប់ពីការស្កេនបណ្ណាល័យ ឬធ្វើការផ្លាស់ប្តូរផ្សេងទៀត ដែលបញ្ជាក់ថា ការកែប្រែ Database អាចធ្វើឱ្យដំណើរការប្រសើរឡើង.", + "TaskRefreshTrickplayImages": "បង្កើតបណ្តុំរូបភាពតាម Trickplay", + "TaskRefreshTrickplayImagesDescription": "បង្កើត​ trickplay previews សម្រាប់​វីដេអូ​ក្នុង​បណ្ណាល័យ​ដែល​បានបង្ហាញ.", + "TaskKeyframeExtractorDescription": "ស្រង់យកFrame គន្លឹះៗពីវីដេអូ ដើម្បីបង្កើតបញ្ជីចាក់ HLS ច្បាស់លាស់ជាងមុន. កិច្ចការនេះអាចនឹងដំណើរការយូរ.", + "FailedLoginAttemptWithUserName": "បរាជ័យក្នុងការព្យាយាមចូលពី {0}", + "TaskCleanTranscode": "សម្អាតថតឯកសារ ​Transcode", + "TaskRefreshChannelsDescription": "Refreshes ព័ត៌មានបណ្តាញអ៊ីនធឺណិត.", + "TaskDownloadMissingSubtitles": "ទាញយកសាប់ថាយថលដែលបាត់", + "TaskRefreshChannels": "Refresh ឆានែល", + "TaskKeyframeExtractor": "ការញែក Keyframe", + "TaskAudioNormalizationDescription": "ស្កែនឯកសារសម្រាប់ធ្វើឱ្យមានតន្ត្រីមានសម្លេងស្មើគ្នា.", + "TaskRefreshLibrary": "ស្កេនបណ្ណាល័យរឿង", + "TaskCleanLogs": "សម្អាត Log Directory", + "TaskRefreshPeople": "Refresh អ្នកប្រើប្រាស់", + "TaskUpdatePlugins": "ធ្វើបច្ចុប្បន្នភាព Plugins", + "TaskUpdatePluginsDescription": "ទាញយក និងដំឡើងបច្ចុប្បន្នភាពសម្រាប់Plugins ដែលត្រូវបាន Config ដើម្បីធ្វើបច្ចុប្បន្នភាពដោយស្វ័យប្រវត្តិ.", + "TaskCleanTranscodeDescription": "លុបឯកសារ Transcode ដែលលើសពីមួយថ្ងៃ.", + "TaskDownloadMissingSubtitlesDescription": "ស្វែងរកតាមអ៊ីនធឺណិត សម្រាប់សាប់ថាយថល ដែលបាត់ដោយផ្អែកលើ metadata.", + "TaskOptimizeDatabase": "ធ្វើឱ្យ Database ប្រសើរឡើង", + "TaskCleanCollectionsAndPlaylistsDescription": "លុបរបស់របរចេញពីបណ្តុំ និងបញ្ជីចាក់ដែលលែងមាន.", + "TaskCleanCollectionsAndPlaylists": "សម្អាតបណ្តុំ និងបញ្ជីចាក់" +} diff --git a/Emby.Server.Implementations/Localization/Core/kn.json b/Emby.Server.Implementations/Localization/Core/kn.json new file mode 100644 index 00000000..9f49be53 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/kn.json @@ -0,0 +1,135 @@ +{ + "TaskDownloadMissingSubtitlesDescription": "ಮೆಟಾಡೇಟಾ ಕಾನ್ಫಿಗರೇಶನ್ ಆಧಾರದ ಮೇಲೆ ಕಾಣೆಯಾದ ಉಪಶೀರ್ಷಿಕೆಗಳಿಗಾಗಿ ಅಂತರ್ಜಾಲದಲ್ಲಿ ಹುಡುಕುತ್ತದೆ.", + "TaskOptimizeDatabase": "ಡೇಟಾಬೇಸ್ ಅನ್ನು ಆಪ್ಟಿಮೈಜ್ ಮಾಡಿ", + "TaskOptimizeDatabaseDescription": "ಡೇಟಾಬೇಸ್ ಅನ್ನು ಕಾಂಪ್ಯಾಕ್ಟ್ ಮಾಡುತ್ತದೆ ಮತ್ತು ಮುಕ್ತ ಜಾಗವನ್ನು ಮೊಟಕುಗೊಳಿಸುತ್ತದೆ. ಲೈಬ್ರರಿಯನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡಿದ ನಂತರ ಈ ಕಾರ್ಯವನ್ನು ನಡೆಸುವುದು ಅಥವಾ ಡೇಟಾಬೇಸ್ ಮಾರ್ಪಾಡುಗಳನ್ನು ಸೂಚಿಸುವ ಇತರ ಬದಲಾವಣೆಗಳನ್ನು ಮಾಡುವುದರಿಂದ ಕಾರ್ಯಕ್ಷಮತೆಯನ್ನು ಸುಧಾರಿಸಬಹುದು.", + "TaskKeyframeExtractor": "ಕೀಫ್ರೇಮ್ ಎಕ್ಸ್‌ಟ್ರಾಕ್ಟರ್", + "TaskKeyframeExtractorDescription": "ಹೆಚ್ಚು ನಿಖರವಾದ HLS ಪ್ಲೇಪಟ್ಟಿಗಳನ್ನು ರಚಿಸಲು ವೀಡಿಯೊ ಫೈಲ್‌ಗಳಿಂದ ಕೀಫ್ರೇಮ್‌ಗಳನ್ನು ಹೊರತೆಗೆಯುತ್ತದೆ. ಈ ಕಾರ್ಯವು ದೀರ್ಘಕಾಲದವರೆಗೆ ನಡೆಯಬಹುದು.", + "ValueHasBeenAddedToLibrary": "{0} ಅನ್ನು ನಿಮ್ಮ ಮಾಧ್ಯಮ ಲೈಬ್ರರಿಗೆ ಸೇರಿಸಲಾಗಿದೆ", + "ValueSpecialEpisodeName": "ವಿಶೇಷ - {0}", + "TasksLibraryCategory": "ಸಮೊಹ", + "TasksApplicationCategory": "ಅಪ್ಲಿಕೇಶನ್", + "TasksChannelsCategory": "ಇಂಟರ್ನೆಟ್ ಚಾನೆಲ್ಗಳು", + "TaskCleanCache": "ಕ್ಲೀನ್ ಕ್ಯಾಶ ಡೈರೆಕ್ಟರಿ", + "TaskCleanCacheDescription": "ಸಿಸ್ಟಮ್‌ಗೆ ಇನ್ನು ಮುಂದೆ ಅಗತ್ಯವಿಲ್ಲದ ಸಂಗ್ರಹ ಫೈಲ್‌ಗಳನ್ನು ಅಳಿಸುತ್ತದೆ.", + "TaskRefreshLibrary": "ಸ್ಕ್ಯಾನ್ ಮೀಡಿಯಾ ಲೈಬ್ರರಿ", + "UserOfflineFromDevice": "{1} ನಿಂದ {0} ಸಂಪರ್ಕ ಕಡಿತಗೊಂಡಿದೆ", + "Albums": "ಸಂಪುಟ", + "Application": "ಅಪ್ಲಿಕೇಶನ್", + "AppDeviceValues": "ಅಪ್ಲಿಕೇಶನ್: {0}, ಸಾಧನ: {1}", + "Artists": "ಕಲಾವಿದರು", + "AuthenticationSucceededWithUserName": "{0} ಯಶಸ್ವಿಯಾಗಿ ದೃಢೀಕರಿಸಲಾಗಿದೆ", + "Books": "ಪುಸ್ತಕಗಳು", + "ChapterNameValue": "ಅಧ್ಯಾಯ {0}", + "Collections": "ಸಂಗ್ರಹಣೆಗಳು", + "Default": "ಪೂರ್ವನಿಯೋಜಿತ", + "DeviceOfflineWithName": "{0} ಸಂಪರ್ಕ ಕಡಿತಗೊಂಡಿದೆ", + "DeviceOnlineWithName": "{0} ಸಂಪರ್ಕಗೊಂಡಿದೆ", + "External": "ಹೊರಗಿನ", + "FailedLoginAttemptWithUserName": "ವಿಫಲ ಲಾಗಿನ್ ಪ್ರಯತ್ನ ಸಂಖ್ಯೆ {0}", + "Favorites": "ಮೆಚ್ಚಿನವುಗಳು", + "Folders": "ಫೋಲ್ಡರ್‌ಗಳು", + "Forced": "ಬಲವಂತವಾಗಿ", + "Genres": "ಪ್ರಕಾರಗಳು", + "HeaderContinueWatching": "ನೋಡುವುದನ್ನು ಮುಂದುವರಿಸಿ", + "HeaderFavoriteAlbums": "ಮೆಚ್ಚಿನ ಸಂಪುಟಗಳು", + "HeaderFavoriteArtists": "ಮೆಚ್ಚಿನ ಕಲಾವಿದರು", + "HeaderFavoriteShows": "ಮೆಚ್ಚಿನ ಪ್ರದರ್ಶನಗಳು", + "HeaderFavoriteSongs": "ಮೆಚ್ಚಿನ ಹಾಡುಗಳು", + "HeaderLiveTV": "ನೇರ ದೂರದರ್ಶನ", + "HeaderNextUp": "ಮುಂದೆ", + "HeaderRecordingGroups": "ರೆಕಾರ್ಡಿಂಗ್ ಗುಂಪುಗಳು", + "MessageApplicationUpdated": "ಜೆಲ್ಲಿಫಿನ್ ಸರ್ವರ್ ಅನ್ನು ನವೀಕರಿಸಲಾಗಿದೆ", + "CameraImageUploadedFrom": "ಹೊಸ ಕ್ಯಾಮರಾ ಚಿತ್ರವನ್ನು {0} ನಿಂದ ಅಪ್‌ಲೋಡ್ ಮಾಡಲಾಗಿದೆ", + "Channels": "ಮೂಲಗಳು", + "HeaderAlbumArtists": "ಸಂಪುಟ ಕಲಾವಿದರು", + "HeaderFavoriteEpisodes": "ಮೆಚ್ಚಿನ ಸಂಚಿಕೆಗಳು", + "HearingImpaired": "ಮೂಗ", + "ItemAddedWithName": "{0} ಅನ್ನು ಸಂಕಲನಕ್ಕೆ ಸೇರಿಸಲಾಗಿದೆ", + "MessageApplicationUpdatedTo": "ಜೆಲ್ಲಿಫಿನ್ ಸರ್ವರ್ ಅನ್ನು {0} ಗೆ ನವೀಕರಿಸಲಾಗಿದೆ", + "MessageNamedServerConfigurationUpdatedWithValue": "ಸರ್ವರ್ ಕಾನ್ಫಿಗರೇಶನ್ ವಿಭಾಗ {0} ಅನ್ನು ನವೀಕರಿಸಲಾಗಿದೆ", + "NewVersionIsAvailable": "ಜೆಲ್ಲಿಫಿನ್ ಸರ್ವರ್‌ನ ಹೊಸ ಆವೃತ್ತಿಯು ಡೌನ್‌ಲೋಡ್‌ಗೆ ಲಭ್ಯವಿದೆ.", + "NotificationOptionAudioPlayback": "ಆಡಿಯೋ ಪ್ಲೇಬ್ಯಾಕ್ ಪ್ರಾರಂಭವಾಗಿದೆ", + "NotificationOptionCameraImageUploaded": "ಕ್ಯಾಮರಾ ಚಿತ್ರವನ್ನು ಅಪ್ಲೋಡ್ ಮಾಡಲಾಗಿದೆ", + "NotificationOptionPluginUninstalled": "ಪ್ಲಗಿನ್ ಅನ್‌ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಲಾಗಿದೆ", + "NotificationOptionUserLockedOut": "ಬಳಕೆದಾರರು ಲಾಕ್ ಔಟ್ ಆಗಿದ್ದಾರೆ", + "NotificationOptionVideoPlaybackStopped": "ವೀಡಿಯೊ ಪ್ಲೇಬ್ಯಾಕ್ ನಿಲ್ಲಿಸಲಾಗಿದೆ", + "PluginUninstalledWithName": "{0} ಅನ್ನು ಅನ್‌ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಲಾಗಿದೆ", + "ScheduledTaskFailedWithName": "{0} ವಿಫಲವಾಗಿದೆ", + "ScheduledTaskStartedWithName": "{0} ಪ್ರಾರಂಭವಾಯಿತು", + "ServerNameNeedsToBeRestarted": "{0} ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಬೇಕಾಗಿದೆ", + "UserCreatedWithName": "ಬಳಕೆದಾರ {0} ಅನ್ನು ರಚಿಸಲಾಗಿದೆ", + "UserLockedOutWithName": "ಬಳಕೆದಾರ {0} ಅನ್ನು ಲಾಕ್ ಮಾಡಲಾಗಿದೆ", + "UserOnlineFromDevice": "{1} ನಿಂದ {0} ಆನ್‌ಲೈನ್‌ನಲ್ಲಿದೆ", + "UserPasswordChangedWithName": "{0} ಬಳಕೆದಾರರಿಗಾಗಿ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಬದಲಾಯಿಸಲಾಗಿದೆ", + "UserPolicyUpdatedWithName": "ಬಳಕೆದಾರರ ನೀತಿಯನ್ನು {0} ಗೆ ನವೀಕರಿಸಲಾಗಿದೆ", + "UserStartedPlayingItemWithValues": "{2} ರಂದು {0} ಆಡುತ್ತಿದೆ {1}", + "UserStoppedPlayingItemWithValues": "{0} ಅವರು {1} ಅನ್ನು {2} ನಲ್ಲಿ ಆಡುವುದನ್ನು ಮುಗಿಸಿದ್ದಾರೆ", + "VersionNumber": "ಆವೃತ್ತಿ {0}", + "TasksMaintenanceCategory": "ನಿರ್ವಹಣೆ", + "TaskCleanActivityLog": "ಕ್ಲೀನ್ ಚಟುವಟಿಕೆ ಲಾಗ್", + "TaskCleanActivityLogDescription": "ಕಾನ್ಫಿಗರ್ ಮಾಡಿದ ವಯಸ್ಸಿಗಿಂತ ಹಳೆಯದಾದ ಚಟುವಟಿಕೆ ಲಾಗ್ ನಮೂದುಗಳನ್ನು ಅಳಿಸುತ್ತದೆ.", + "TaskRefreshChapterImages": "ಅಧ್ಯಾಯ ಚಿತ್ರಗಳನ್ನು ಹೊರತೆಗೆಯಿರಿ", + "TaskRefreshChapterImagesDescription": "ಅಧ್ಯಾಯಗಳನ್ನು ಹೊಂದಿರುವ ವೀಡಿಯೊಗಳಿಗಾಗಿ ಥಂಬ್‌ನೇಲ್‌ಗಳನ್ನು ರಚಿಸುತ್ತದೆ.", + "TaskRefreshLibraryDescription": "ಹೊಸ ಫೈಲ್‌ಗಳಿಗಾಗಿ ನಿಮ್ಮ ಮೀಡಿಯಾ ಲೈಬ್ರರಿಯನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡುತ್ತದೆ ಮತ್ತು ಮೆಟಾಡೇಟಾವನ್ನು ರಿಫ್ರೆಶ್ ಮಾಡುತ್ತದೆ.", + "TaskCleanLogsDescription": "{0} ದಿನಗಳಿಗಿಂತ ಹಳೆಯದಾದ ಲಾಗ್ ಫೈಲ್‌ಗಳನ್ನು ಅಳಿಸುತ್ತದೆ.", + "TaskUpdatePluginsDescription": "ಸ್ವಯಂಚಾಲಿತವಾಗಿ ನವೀಕರಿಸಲು ಕಾನ್ಫಿಗರ್ ಮಾಡಲಾದ ಪ್ಲಗಿನ್‌ಗಳಿಗಾಗಿ ನವೀಕರಣಗಳನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡುತ್ತದೆ ಮತ್ತು ಸ್ಥಾಪಿಸುತ್ತದೆ.", + "TaskCleanTranscodeDescription": "ಒಂದು ದಿನಕ್ಕಿಂತ ಹಳೆಯದಾದ ಟ್ರಾನ್ಸ್‌ಕೋಡ್ ಫೈಲ್‌ಗಳನ್ನು ಅಳಿಸುತ್ತದೆ.", + "TaskDownloadMissingSubtitles": "ಕಾಣೆಯಾದ ಉಪಶೀರ್ಷಿಕೆಗಳನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ", + "Shows": "ಧಾರವಾಹಿಗಳು", + "Songs": "ಹಾಡುಗಳು", + "StartupEmbyServerIsLoading": "ಜೆಲ್ಲಿಫಿನ್ ಸರ್ವರ್ ಲೋಡ್ ಆಗುತ್ತಿದೆ. ದಯವಿಟ್ಟು ಸ್ವಲ್ಪ ಸಮಯದ ನಂತರ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ.", + "UserDeletedWithName": "ಬಳಕೆದಾರ {0} ಅನ್ನು ಅಳಿಸಲಾಗಿದೆ", + "UserDownloadingItemWithValues": "{0} ಡೌನ್‌ಲೋಡ್ ಆಗುತ್ತಿದೆ {1}", + "SubtitleDownloadFailureFromForItem": "ಉಪಶೀರ್ಷಿಕೆಗಳು {0} ನಿಂದ {1} ಗಾಗಿ ಡೌನ್‌ಲೋಡ್ ಮಾಡಲು ವಿಫಲವಾಗಿವೆ", + "Sync": "ಹೊಂದಿಕೆ", + "System": "ವ್ಯವಸ್ಥೆ", + "TvShows": "ದೂರದರ್ಶನ ಕಾರ್ಯಕ್ರಮಗಳು", + "Undefined": "ವ್ಯಾಖ್ಯಾನಿಸಲಾಗಿಲ್ಲ", + "User": "ಬಳಕೆದಾರ", + "HomeVideos": "ಮುಖಪುಟ ವೀಡಿಯೊಗಳು", + "Inherit": "ಪಾರಂಪರ್ಯವಾಗಿ", + "ItemRemovedWithName": "{0} ಅನ್ನು ಸಂಕಲನದಿಂದ ತೆಗೆದುಹಾಕಲಾಗಿದೆ", + "LabelIpAddressValue": "IP ವಿಳಾಸ: {0}", + "LabelRunningTimeValue": "ಅವಧಿ: {0}", + "Latest": "ಹೊಸದಾದ", + "MessageServerConfigurationUpdated": "ಸರ್ವರ್ ಕಾನ್ಫಿಗರೇಶನ್ ಅನ್ನು ನವೀಕರಿಸಲಾಗಿದೆ", + "MixedContent": "ಮಿಶ್ರ ವಿಷಯ", + "Movies": "ಚಲನಚಿತ್ರಗಳು", + "Music": "ಸಂಗೀತ", + "MusicVideos": "ಸಂಗೀತ ವೀಡಿಯೊಗಳು", + "NameInstallFailed": "{0} ಸ್ಥಾಪನೆ ವಿಫಲವಾಗಿದೆ", + "NameSeasonNumber": "ಸೀಸನ್ {0}", + "NameSeasonUnknown": "ಸೀಸನ್ ತಿಳಿದಿಲ್ಲ", + "NotificationOptionApplicationUpdateAvailable": "ಅಪ್ಲಿಕೇಶನ್ ನವೀಕರಣ ಲಭ್ಯವಿದೆ", + "NotificationOptionApplicationUpdateInstalled": "ಅಪ್ಲಿಕೇಶನ್ ನವೀಕರಣವನ್ನು ಸ್ಥಾಪಿಸಲಾಗಿದೆ", + "NotificationOptionAudioPlaybackStopped": "ಆಡಿಯೋ ಪ್ಲೇಬ್ಯಾಕ್ ನಿಲ್ಲಿಸಲಾಗಿದೆ", + "NotificationOptionInstallationFailed": "ಸ್ಥಾಪನ ವೈಫಲ್ಯ", + "NotificationOptionNewLibraryContent": "ಹೊಸ ವಿಷಯವನ್ನು ಒಳಗೊಂಡಿದೆ", + "NotificationOptionPluginError": "ಪ್ಲಗಿನ್ ವೈಫಲ್ಯ", + "NotificationOptionPluginInstalled": "ಪ್ಲಗಿನ್ ವೈಫಲ್ಯ", + "NotificationOptionPluginUpdateInstalled": "ಪ್ಲಗಿನ್ ನವೀಕರಣವನ್ನು ಸ್ಥಾಪಿಸಲಾಗಿದೆ", + "NotificationOptionServerRestartRequired": "ಸರ್ವರ್ ಮರುಪ್ರಾರಂಭದ ಅಗತ್ಯವಿದೆ", + "NotificationOptionTaskFailed": "ನಿಗದಿತ ಕಾರ್ಯ ವೈಫಲ್ಯ", + "NotificationOptionVideoPlayback": "ವೀಡಿಯೊ ಪ್ಲೇಬ್ಯಾಕ್ ಪ್ರಾರಂಭವಾಗಿದೆ", + "Photos": "ಚಿತ್ರಗಳು", + "Playlists": "ಪ್ಲೇಪಟ್ಟಿಗಳು", + "Plugin": "ಪ್ಲಗಿನ್", + "PluginInstalledWithName": "{0} ಅನ್ನು ಸ್ಥಾಪಿಸಲಾಗಿದೆ", + "PluginUpdatedWithName": "{0} ಅನ್ನು ನವೀಕರಿಸಲಾಗಿದೆ", + "ProviderValue": "ಒದಗಿಸುವವರು: {0}", + "TaskCleanLogs": "ಕ್ಲೀನ್ ಲಾಗ್ ಡೈರೆಕ್ಟರಿ", + "TaskRefreshPeople": "ಜನರನ್ನು ರಿಫ್ರೆಶ್ ಮಾಡಿ", + "TaskRefreshPeopleDescription": "ನಿಮ್ಮ ಮಾಧ್ಯಮ ಲೈಬ್ರರಿಯಲ್ಲಿ ನಟರು ಮತ್ತು ನಿರ್ದೇಶಕರಿಗಾಗಿ ಮೆಟಾಡೇಟಾವನ್ನು ನವೀಕರಿಸಿ.", + "TaskUpdatePlugins": "ಪ್ಲಗಿನ್‌ಗಳನ್ನು ನವೀಕರಿಸಿ", + "TaskCleanTranscode": "ಟ್ರಾನ್ಸ್‌ಕೋಡ್ ಡೈರೆಕ್ಟರಿಯನ್ನು ಸ್ವಚ್ಛಗೊಳಿಸಿ", + "TaskRefreshChannels": "ಚಾನಲ್‌ಗಳನ್ನು ರಿಫ್ರೆಶ್ ಮಾಡಿ", + "TaskRefreshChannelsDescription": "ಇಂಟರ್ನೆಟ್ ಚಾನಲ್ ಮಾಹಿತಿಯನ್ನು ರಿಫ್ರೆಶ್ ಮಾಡುತ್ತದೆ.", + "TaskAudioNormalizationDescription": "ಧ್ವನಿ ಸಾಮಾನ್ಯೀಕರಣ ಮಾಹಿತಿಗಾಗಿ ಕಡತ‌ಗಳನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡುತ್ತದೆ.", + "TaskDownloadMissingLyricsDescription": "ಹಾಡುಗಳಿಗೆ ಸಾಹಿತ್ಯ ಪಡೆಯಿರಿ", + "TaskExtractMediaSegments": "ಮಾಧ್ಯಮ ವಿಭಾಗದ ಹುಡುಕು", + "TaskDownloadMissingLyrics": "ಇಲ್ಲದ ಸಾಹಿತ್ಯವನ್ನು ಪಡೆಯಿರಿ", + "TaskAudioNormalization": "ಧ್ವನಿ ಸಾಮಾನ್ಯೀಕರಣ", + "TaskRefreshTrickplayImages": "ಟ್ರಿಕ್‌ಪ್ಲೇ ಚಿತ್ರಗಳನ್ನು ರಚಿಸಿ", + "TaskCleanCollectionsAndPlaylists": "ಸಂಗ್ರಹಗಳು ಮತ್ತು ಪ್ಲೇಪಟ್ಟಿಗಳನ್ನು ಸ್ವಚ್ಛಗೊಳಿಸಿ", + "TaskCleanCollectionsAndPlaylistsDescription": "ಇಲ್ಲದ ಸಂಗ್ರಹಗಳು ಮತ್ತು ಪ್ಲೇಪಟ್ಟಿಗಳಿಂದ ವಸ್ತುಗಳನ್ನು ತೆಗೆದುಹಾಕುತ್ತದೆ." +} diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json new file mode 100644 index 00000000..2b24ea2c --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ko.json @@ -0,0 +1,141 @@ +{ + "Albums": "앨범", + "AppDeviceValues": "앱: {0}, 장치: {1}", + "Application": "애플리케이션", + "Artists": "아티스트", + "AuthenticationSucceededWithUserName": "{0} 사용자가 성공적으로 인증됨", + "Books": "도서", + "CameraImageUploadedFrom": "{0}에서 새로운 카메라 이미지가 업로드됨", + "Channels": "채널", + "ChapterNameValue": "챕터 {0}", + "Collections": "컬렉션", + "DeviceOfflineWithName": "{0}의 연결 끊김", + "DeviceOnlineWithName": "{0}이(가) 연결됨", + "FailedLoginAttemptWithUserName": "{0}에서 로그인 실패", + "Favorites": "즐겨찾기", + "Folders": "폴더", + "Genres": "장르", + "HeaderAlbumArtists": "앨범 음악가", + "HeaderContinueWatching": "계속 시청하기", + "HeaderFavoriteAlbums": "즐겨찾는 앨범", + "HeaderFavoriteArtists": "즐겨찾는 아티스트", + "HeaderFavoriteEpisodes": "즐겨찾는 에피소드", + "HeaderFavoriteShows": "즐겨찾는 쇼", + "HeaderFavoriteSongs": "즐겨찾는 노래", + "HeaderLiveTV": "실시간 TV", + "HeaderNextUp": "다음으로", + "HeaderRecordingGroups": "녹화 그룹", + "HomeVideos": "홈 비디오", + "Inherit": "상속", + "ItemAddedWithName": "{0}가 라이브러리에 추가되었습니다", + "ItemRemovedWithName": "{0}가 라이브러리에서 제거됨", + "LabelIpAddressValue": "IP 주소: {0}", + "LabelRunningTimeValue": "상영 시간: {0}", + "Latest": "최근", + "MessageApplicationUpdated": "Jellyfin 서버가 업데이트되었습니다", + "MessageApplicationUpdatedTo": "Jellyfin 서버가 {0}로 업데이트되었습니다", + "MessageNamedServerConfigurationUpdatedWithValue": "서버 환경 설정 {0} 섹션이 업데이트되었습니다", + "MessageServerConfigurationUpdated": "서버 환경 설정이 업데이트되었습니다", + "MixedContent": "혼합 콘텐츠", + "Movies": "영화", + "Music": "음악", + "MusicVideos": "뮤직비디오", + "NameInstallFailed": "{0} 설치 실패", + "NameSeasonNumber": "시즌 {0}", + "NameSeasonUnknown": "알 수 없는 시즌", + "NewVersionIsAvailable": "새로운 버전의 Jellyfin 서버를 사용할 수 있습니다.", + "NotificationOptionApplicationUpdateAvailable": "애플리케이션 업데이트 가능", + "NotificationOptionApplicationUpdateInstalled": "애플리케이션 업데이트 완료", + "NotificationOptionAudioPlayback": "오디오 재생 시작됨", + "NotificationOptionAudioPlaybackStopped": "오디오 재생 중지됨", + "NotificationOptionCameraImageUploaded": "카메라 이미지가 업로드됨", + "NotificationOptionInstallationFailed": "설치 실패", + "NotificationOptionNewLibraryContent": "새 콘텐츠가 추가됨", + "NotificationOptionPluginError": "플러그인 실패", + "NotificationOptionPluginInstalled": "플러그인 설치됨", + "NotificationOptionPluginUninstalled": "플러그인 제거됨", + "NotificationOptionPluginUpdateInstalled": "플러그인 업데이트 완료", + "NotificationOptionServerRestartRequired": "서버 재시작 필요", + "NotificationOptionTaskFailed": "예약 작업 실패", + "NotificationOptionUserLockedOut": "잠긴 사용자", + "NotificationOptionVideoPlayback": "비디오 재생 시작됨", + "NotificationOptionVideoPlaybackStopped": "비디오 재생 중지됨", + "Photos": "사진", + "Playlists": "재생목록", + "Plugin": "플러그인", + "PluginInstalledWithName": "{0} 설치됨", + "PluginUninstalledWithName": "{0} 제거됨", + "PluginUpdatedWithName": "{0} 업데이트됨", + "ProviderValue": "제공자: {0}", + "ScheduledTaskFailedWithName": "{0} 실패", + "ScheduledTaskStartedWithName": "{0} 시작", + "ServerNameNeedsToBeRestarted": "{0}를 재시작해야합니다", + "Shows": "시리즈", + "Songs": "노래", + "StartupEmbyServerIsLoading": "Jellyfin 서버를 불러오고 있습니다. 잠시 후에 다시 시도하십시오.", + "SubtitleDownloadFailureFromForItem": "{0}에서 {1} 자막 다운로드에 실패했습니다", + "Sync": "동기화", + "System": "시스템", + "TvShows": "TV 쇼", + "User": "사용자", + "UserCreatedWithName": "사용자 {0} 생성됨", + "UserDeletedWithName": "사용자 {0} 삭제됨", + "UserDownloadingItemWithValues": "{0} 사용자가 {1} 다운로드 중", + "UserLockedOutWithName": "{0} 사용자 잠김", + "UserOfflineFromDevice": "{0} 사용자의 {1}에서 연결이 끊김", + "UserOnlineFromDevice": "{0} 사용자가 {1}에서 접속함", + "UserPasswordChangedWithName": "{0} 사용자 비밀번호 변경됨", + "UserPolicyUpdatedWithName": "{0} 사용자 정책 업데이트됨", + "UserStartedPlayingItemWithValues": "{0} 사용자의 {2}에서 {1} 재생 중", + "UserStoppedPlayingItemWithValues": "{0} 사용자의 {2}에서 {1} 재생을 마침", + "ValueHasBeenAddedToLibrary": "{0}가 미디어 라이브러리에 추가되었습니다", + "ValueSpecialEpisodeName": "스페셜 - {0}", + "VersionNumber": "버전 {0}", + "TasksApplicationCategory": "어플리케이션", + "TasksMaintenanceCategory": "유지 보수", + "TaskDownloadMissingSubtitlesDescription": "메타 데이터 기반으로 누락 된 자막이 있는지 인터넷을 검색합니다.", + "TaskDownloadMissingSubtitles": "누락 된 자막 다운로드", + "TaskRefreshChannelsDescription": "인터넷 채널 정보를 새로 고칩니다.", + "TaskRefreshChannels": "채널 새로고침", + "TaskCleanTranscodeDescription": "하루 이상 지난 트랜스 코드 파일을 삭제합니다.", + "TaskCleanTranscode": "트랜스코드 폴더 청소", + "TaskUpdatePluginsDescription": "자동으로 업데이트되도록 구성된 플러그인 업데이트를 다운로드하여 설치합니다.", + "TaskUpdatePlugins": "플러그인 업데이트", + "TaskRefreshPeopleDescription": "미디어 라이브러리에서 배우 및 감독의 메타 데이터를 업데이트합니다.", + "TaskRefreshPeople": "인물 새로고침", + "TaskCleanLogsDescription": "{0} 일이 지난 로그 파일을 삭제합니다.", + "TaskCleanLogs": "로그 폴더 청소", + "TaskRefreshLibraryDescription": "미디어 라이브러리에서 새 파일을 검색하고 메타 데이터를 새로 고칩니다.", + "TaskRefreshLibrary": "미디어 라이브러리 스캔", + "TaskRefreshChapterImagesDescription": "챕터가있는 비디오의 썸네일을 만듭니다.", + "TaskRefreshChapterImages": "챕터 이미지 추출", + "TaskCleanCacheDescription": "시스템에서 더 이상 필요하지 않은 캐시 파일을 삭제합니다.", + "TaskCleanCache": "캐시 폴더 청소", + "TasksChannelsCategory": "인터넷 채널", + "TasksLibraryCategory": "라이브러리", + "TaskCleanActivityLogDescription": "구성된 기간보다 오래된 활동내역 삭제.", + "TaskCleanActivityLog": "활동내역청소", + "Undefined": "일치하지 않음", + "Forced": "강제하기", + "Default": "기본 설정", + "TaskOptimizeDatabaseDescription": "데이터베이스를 압축하고 사용 가능한 공간을 늘립니다. 라이브러리를 검색한 후 이 작업을 실행하거나 데이터베이스 수정같은 비슷한 작업을 수행하면 성능이 향상될 수 있습니다.", + "TaskOptimizeDatabase": "데이터베이스 최적화", + "TaskKeyframeExtractorDescription": "비디오 파일에서 키프레임을 추출하여 더 정확한 HLS 재생 목록을 만듭니다. 이 작업은 오랫동안 진행될 수 있습니다.", + "TaskKeyframeExtractor": "키프레임 추출", + "External": "외부", + "HearingImpaired": "청각 장애", + "TaskCleanCollectionsAndPlaylists": "컬렉션과 재생목록 정리", + "TaskAudioNormalization": "오디오의 볼륨 수준을 일정하게 조정", + "TaskAudioNormalizationDescription": "오디오의 볼륨 수준을 일정하게 조정하기 위해 파일을 스캔합니다.", + "TaskRefreshTrickplayImages": "비디오 탐색용 미리보기 썸네일 생성", + "TaskRefreshTrickplayImagesDescription": "활성화된 라이브러리에서 비디오의 트릭플레이 미리보기를 생성합니다.", + "TaskCleanCollectionsAndPlaylistsDescription": "더 이상 존재하지 않는 컬렉션 및 재생 목록에서 항목을 제거합니다.", + "TaskExtractMediaSegments": "미디어 세그먼트 스캔", + "TaskExtractMediaSegmentsDescription": "MediaSegment를 지원하는 플러그인에서 미디어 세그먼트를 추출하거나 가져옵니다.", + "TaskMoveTrickplayImages": "트릭플레이 이미지 위치 마이그레이션", + "TaskMoveTrickplayImagesDescription": "추출된 트릭플레이 이미지를 라이브러리 설정에 따라 이동합니다.", + "TaskDownloadMissingLyrics": "누락된 가사 다운로드", + "TaskDownloadMissingLyricsDescription": "가사 다운로드", + "CleanupUserDataTask": "사용자 데이터 정리 작업", + "CleanupUserDataTaskDescription": "최소 90일 이상 존재하지 않는 미디어에 대한 사용자 데이터(시청 상태, 즐겨찾기 등)를 정리합니다." +} diff --git a/Emby.Server.Implementations/Localization/Core/kw.json b/Emby.Server.Implementations/Localization/Core/kw.json new file mode 100644 index 00000000..336d286f --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/kw.json @@ -0,0 +1,139 @@ +{ + "Collections": "Kuntellow", + "DeviceOfflineWithName": "{0} re anjunyas", + "External": "A-ves", + "Folders": "Plegellow", + "HeaderFavoriteAlbums": "Albomow Drudh", + "HeaderFavoriteArtists": "Artydhyon Drudh", + "HeaderFavoriteEpisodes": "Towlennow Drudh", + "HeaderFavoriteSongs": "Kanow Drudh", + "HeaderRecordingGroups": "Bagasow Rekordya", + "HearingImpaired": "Klewans Aperys", + "HomeVideos": "Gwydhyow Tre", + "Inherit": "Herya", + "LabelRunningTimeValue": "Prys ow ponya: {0}", + "Latest": "Diwettha", + "MessageApplicationUpdated": "Servell Jellyfin re beu nowedhys", + "MessageApplicationUpdatedTo": "Servell Jellyfin re beu nowedhys dhe {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Rann dewisyans servell {0} re beu nowedhys", + "MixedContent": "Dalgh kemmyskys", + "Movies": "Fylmow", + "MusicVideos": "Gwydhyow Ilow", + "NameSeasonUnknown": "Seson Anwodhvedhys", + "NotificationOptionAudioPlayback": "Seneans dallethys", + "NotificationOptionAudioPlaybackStopped": "Seneans hedhys", + "NotificationOptionPluginError": "Defowt ystynnans", + "NotificationOptionPluginUninstalled": "Ystynnans anynstallys", + "NotificationOptionPluginUpdateInstalled": "Nowedheans ystynnans ynstallys", + "Application": "Gweythres", + "Favorites": "Moyha Kerys", + "Forced": "Konstrynys", + "Albums": "Albomow", + "Books": "Lyvrow", + "Channels": "Kanolyow", + "AppDeviceValues": "App: {0}, Devis: {1}", + "Artists": "Artyhdyon", + "HeaderAlbumArtists": "Albom artydhyon", + "HeaderNextUp": "Nessa", + "CameraImageUploadedFrom": "Skeusen kamera nowydh re beu ughkargys a-dhyworth {0}", + "ChapterNameValue": "Chaptra {0}", + "FailedLoginAttemptWithUserName": "Assay omgelm fyllys a-dhyworth {0}", + "AuthenticationSucceededWithUserName": "{0} omgelmys yn sewen", + "Default": "Defowt", + "DeviceOnlineWithName": "{0} yw junys", + "ItemRemovedWithName": "{0} a veu dileys a-dhyworth an lyverva", + "LabelIpAddressValue": "Trigva PK: {)}", + "Music": "Ilow", + "HeaderContinueWatching": "Pesya Ow Kweles", + "NameSeasonNumber": "Seson {0}", + "NotificationOptionApplicationUpdateInstalled": "Nowedheans gweythres ynstallys", + "NotificationOptionCameraImageUploaded": "Skeusen kamera ughkargys", + "HeaderFavoriteShows": "Diskwedhyansow Drudh", + "HeaderLiveTV": "PW Yn Fyw", + "MessageServerConfigurationUpdated": "Dewisyans servell re beu nowedhys", + "ItemAddedWithName": "{0} a veu keworrys dhe'n lyverva", + "NameInstallFailed": "{0} ynstallyans fyllys", + "NotificationOptionNewLibraryContent": "Dalgh nowydh keworrys", + "NewVersionIsAvailable": "Yma versyon nowydh a Servell Jellyfin neb yw kavadow rag iskarga.", + "NotificationOptionApplicationUpdateAvailable": "Nowedheans gweythres kavadow", + "NotificationOptionInstallationFailed": "Defowt ynstallyans", + "Genres": "Eghennow", + "NotificationOptionPluginInstalled": "Ystynnans ynstallys", + "NotificationOptionServerRestartRequired": "Dastalleth servell yw res", + "StartupEmbyServerIsLoading": "Yma Servell Jellyfin ow kargya. Assay arta yn berr mar pleg.", + "SubtitleDownloadFailureFromForItem": "Istitlow a fyllis iskarga a-dhyworth {0] rag {1}", + "System": "Kevreyth", + "User": "Devnydhyer", + "UserDeletedWithName": "Devnydhyer {0} re beu dileys", + "UserLockedOutWithName": "Devnydhyer {0} re beu alhwedhys yn-mes", + "UserStoppedPlayingItemWithValues": "{0} re worfennas gwari {1} war {2}", + "UserOfflineFromDevice": "{0} re anjunyas a-dhyworth {1}", + "UserOnlineFromDevice": "{0} yw warlinen a-dhyworth {1}", + "NotificationOptionUserLockedOut": "Devnydhyer yw alhwedhys yn-mes", + "Photos": "Skeusennow", + "Playlists": "Rolyow-gwari", + "Plugin": "Ystynnans", + "PluginInstalledWithName": "{0} a veu ynstallys", + "UserPolicyUpdatedWithName": "Polici devnydhyer re beu nowedhys rag {0}", + "PluginUpdatedWithName": "{0} a veu nowedhys", + "ScheduledTaskFailedWithName": "{0} a fyllis", + "Songs": "Kanow", + "Sync": "Kesseni", + "TvShows": "Towlennow PW", + "Undefined": "Anstyrys", + "UserCreatedWithName": "Devnydhyer {0} re beu gwruthys", + "UserDownloadingItemWithValues": "Yma {0} owth iskarga {1}", + "UserPasswordChangedWithName": "Ger-tremena re beu chanjys rag devnydhyer {0}", + "UserStartedPlayingItemWithValues": "Yma {0} ow kwari {1} war {2}", + "ValueHasBeenAddedToLibrary": "{0} re beu keworrys dhe'th lyverva media", + "VersionNumber": "Versyon {0}", + "TasksLibraryCategory": "Lyverva", + "TaskCleanActivityLog": "Glanhe Kovlyver Gwrians", + "TaskRefreshPeople": "Disegha Tus", + "TaskRefreshLibrary": "Arhwilas Lyverva Media", + "TaskCleanTranscodeDescription": "Y hwra dilea restrennow treylya neg a veu gwrys kyns nans yw dydh.", + "NotificationOptionVideoPlaybackStopped": "Gwareans gwydhyow yw hedhys", + "NotificationOptionVideoPlayback": "Gwareans gwydhyow yw dallethys", + "PluginUninstalledWithName": "{0} a veu anynstallys", + "NotificationOptionTaskFailed": "Defowt oberen towlennys", + "ProviderValue": "Provier: {0}", + "ScheduledTaskStartedWithName": "{0} a dhallathas", + "ServerNameNeedsToBeRestarted": "Yma edhom dhe {0} a vos dastallathys", + "ValueSpecialEpisodeName": "Arbennik - {0}", + "TasksMaintenanceCategory": "Mentons", + "TasksApplicationCategory": "Gweythres", + "TasksChannelsCategory": "Kanolyow Kesrosweyth", + "TaskCleanLogs": "Glanhe Kevarwodhyador Kovlyver", + "TaskAudioNormalization": "Normalheans Klewans", + "TaskRefreshChannels": "Disegha Kanolyow", + "TaskCleanTranscode": "Glanhe Kevarwodhyador Treylya", + "TaskUpdatePlugins": "Nowedhi Ystynansow", + "Shows": "Diskwedhyansow", + "TaskCleanCache": "Glanhe Kevarwodhyador Gwithva", + "TaskCleanActivityLogDescription": "Y hwra dilea lin kovlyver gwrians kottha ages an bloodh dewisys.", + "TaskCleanCacheDescription": "Y hwra dilea restrennow gwithva nag yw res rag an kevreyth.", + "TaskRefreshPeopleDescription": "Y hwra nowedhi metadata rag gwarioryon ha kevarwodhoryon yn dha lyverva media.", + "TaskRefreshChapterImages": "Kuntel Imajys Chaptra", + "TaskRefreshChapterImagesDescription": "Y hwra ewines meus rag gwydhyowyow gans chaptraow.", + "TaskRefreshTrickplayImagesDescription": "Y hwra kynwelyow trickplay rag gwydhyowyow yn lyvervaow gallosegys.", + "TaskRefreshTrickplayImages": "Dinythi Imajys Trickplay", + "TaskCleanLogsDescription": "Y hwra dilea restrennow kovlyver a veu gwrys kyns nans yw {0} dydh.", + "TaskDownloadMissingLyrics": "Iskarga geryow kellys", + "TaskUpdatePluginsDescription": "Y hwra iskarga hag ynstallya nowedheansow rag ystynansow neb yw dewisys dhe nowedhi yn awtomatek.", + "TaskDownloadMissingSubtitles": "Iskarga istitlow kellys", + "TaskRefreshChannelsDescription": "Y hwra disegha kedhlow kanolyow kesrosweyth.", + "TaskDownloadMissingLyricsDescription": "Y hwra iskarga geryow rag kanow", + "TaskDownloadMissingSubtitlesDescription": "Y hwra hwilas an kesrosweyth rag istitlow kellys a-dhywoth dewisyans metadata.", + "TaskOptimizeDatabase": "Gwellhe selvanylyon", + "TaskOptimizeDatabaseDescription": "Y hwra kesstrotha ha berrhe efander rydh. Martesen y hwra gwellhe gwryth mar kwre'ta an oberen ma wosa ty dhe arhwilas an lyverva, po neb chanj aral neb a brof chanjyansow selvanylyon.", + "TaskAudioNormalizationDescription": "Y hwra arhwilas restrennow rag manylyon normalheans klewans.", + "TaskRefreshLibraryDescription": "Y hwra arhwilas dha lyverva media rag restrennow nowydh ha disegha metamanylyon.", + "TaskCleanCollectionsAndPlaylists": "Glanhe kuntellow ha rolyow-gwari", + "TaskKeyframeExtractor": "Estennell Framalhwedh", + "TaskCleanCollectionsAndPlaylistsDescription": "Y hwra dilea taklow a-dhyworth kuntellow ha rolyow-gwari na vos na moy.", + "TaskKeyframeExtractorDescription": "Y hwra kuntel framyowalhwedh a-dhyworth restrennow gwydhyowyow rag gul rolyow-gwari HLS moy poran. Martesen y hwra an oberen ma ow ponya rag termyn hir.", + "TaskExtractMediaSegments": "Arhwilas Rann Media", + "TaskExtractMediaSegmentsDescription": "Kavos rannow media a-dhyworth ystynansow gallosegys MediaSegment.", + "TaskMoveTrickplayImages": "Divroa Tyller Imach TrickPlay", + "TaskMoveTrickplayImagesDescription": "Y hwra movya restrennow a-lemmyn trickplay herwydh settyansow lyverva." +} diff --git a/Emby.Server.Implementations/Localization/Core/ky.json b/Emby.Server.Implementations/Localization/Core/ky.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ky.json @@ -0,0 +1 @@ +{} diff --git a/Emby.Server.Implementations/Localization/Core/lb.json b/Emby.Server.Implementations/Localization/Core/lb.json new file mode 100644 index 00000000..176f2ba2 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/lb.json @@ -0,0 +1,139 @@ +{ + "Albums": "Alben", + "Application": "Applikatioun", + "Artists": "Kënschtler", + "Books": "Bicher", + "Channels": "Kanäl", + "Collections": "Kollektiounen", + "Default": "Standard", + "ChapterNameValue": "Kapitel {0}", + "DeviceOnlineWithName": "{0} ass Online", + "DeviceOfflineWithName": "{0} ass Offline", + "External": "Extern", + "Favorites": "Favoritten", + "Folders": "Dossieren", + "Forced": "Forcéiert", + "HeaderAlbumArtists": "Album Kënschtler", + "HeaderFavoriteAlbums": "Léifsten Alben", + "HeaderFavoriteArtists": "Léifsten Kënschtler", + "HeaderFavoriteEpisodes": "Léifsten Episoden", + "HeaderFavoriteShows": "Léifsten Shows", + "HeaderFavoriteSongs": "Léifsten Lidder", + "Genres": "Generen", + "HeaderContinueWatching": "Weider kucken", + "Inherit": "Iwwerhuelen", + "HeaderNextUp": "Als Nächst", + "HeaderRecordingGroups": "Opname Gruppen", + "HearingImpaired": "Daaf", + "HomeVideos": "Amateur Videoen", + "ItemRemovedWithName": "Element ewech geholl: {0}", + "LabelIpAddressValue": "IP Adress: {0}", + "LabelRunningTimeValue": "Lafzäit: {0}", + "Latest": "Dat Aktuellst", + "MessageApplicationUpdatedTo": "Jellyfin Server aktualiséiert op {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Server Konfiguratiounssektioun {0} aktualiséiert", + "MessageServerConfigurationUpdated": "Server Konfiguratioun aktualiséiert", + "Movies": "Filmer", + "Music": "Musek", + "NameInstallFailed": "{0} Installatioun net gelongen", + "NameSeasonNumber": "Staffel {0}", + "NameSeasonUnknown": "Staffel Onbekannt", + "MusicVideos": "Museksvideoen", + "NotificationOptionApplicationUpdateAvailable": "Applikatiouns Update verfügbar", + "NotificationOptionApplicationUpdateInstalled": "Applikatiouns Update nët Installéiert", + "NotificationOptionAudioPlayback": "Audio ofspillen gestart", + "NotificationOptionAudioPlaybackStopped": "Audio ofspillen gestoppt", + "NotificationOptionCameraImageUploaded": "Kamera Bild eropgelueden", + "NotificationOptionInstallationFailed": "Installatioun net gelongen", + "NotificationOptionNewLibraryContent": "Neien Bibliothéik Inhalt", + "NotificationOptionPluginError": "Plugin Feeler", + "NotificationOptionPluginInstalled": "Plugin installéiert", + "NotificationOptionPluginUninstalled": "Plugin desinstalléiert", + "NotificationOptionPluginUpdateInstalled": "Plugin Update installéiert", + "Photos": "Fotoen", + "NotificationOptionTaskFailed": "Aufgab net gelongen", + "NotificationOptionUserLockedOut": "Benotzer Gesperrt", + "NotificationOptionVideoPlaybackStopped": "Video ofspillen gestoppt", + "NotificationOptionVideoPlayback": "Video ofspillen gestartet", + "Plugin": "Plugin", + "PluginUninstalledWithName": "{0} desinstalléiert", + "PluginUpdatedWithName": "{0} aktualiséiert", + "ProviderValue": "Provider: {0}", + "ScheduledTaskFailedWithName": "Aufgab: {0} net gelongen", + "Playlists": "Playlëschten", + "Shows": "Shows", + "Songs": "Lidder", + "ServerNameNeedsToBeRestarted": "{0} muss nei gestart ginn", + "StartupEmbyServerIsLoading": "Jellyfin Server luedt. Probéier méi spéit nach eng Kéier.", + "Sync": "Synchroniséieren", + "System": "System", + "User": "Benotzer", + "TvShows": "TV Shows", + "Undefined": "Net definéiert", + "UserCreatedWithName": "Benotzer {0} erstellt", + "UserDownloadingItemWithValues": "{0} luet {1} erof", + "UserOfflineFromDevice": "{0} Benotzer Offline um Gerät {1}", + "UserLockedOutWithName": "Benotzer {0} gesperrt", + "UserOnlineFromDevice": "{0} Benotzer Online um Gerät {1}", + "UserPasswordChangedWithName": "Benotzer Passwuert geännert fir {0}", + "UserPolicyUpdatedWithName": "Benotzer Politik aktualiséiert fir: {0}", + "UserStartedPlayingItemWithValues": "{0} spillt {1} op {2} oof", + "ValueHasBeenAddedToLibrary": "{0} der Bibliothéik bäigefüügt", + "VersionNumber": "Versioun {0}", + "TasksMaintenanceCategory": "Ënnerhalt", + "TasksLibraryCategory": "Bibliothéik", + "ValueSpecialEpisodeName": "Spezial-Episodenumm", + "TasksChannelsCategory": "Internet Kanäl", + "TaskCleanActivityLog": "Aktivitéits Log botzen", + "TaskCleanActivityLogDescription": "Läscht Aktivitéitslogs méi al wéi konfiguréiert.", + "TaskCleanCache": "Aufgab Cache Botzen", + "TaskRefreshChapterImages": "Kapitel Biller erstellen", + "TaskRefreshChapterImagesDescription": "Erstellt Miniaturbiller fir Videoen, déi Kapitelen hunn.", + "TaskAudioNormalization": "Audio Normaliséierung", + "TaskRefreshLibrary": "Bibliothéik aktualiséieren", + "TaskRefreshLibraryDescription": "Scannt deng Mediebibliothéik no neien Dateien a frëscht d’Metadata op.", + "TaskCleanLogs": "Log Dateien botzen", + "TaskRefreshPeople": "Persounen aktualiséieren", + "TaskRefreshPeopleDescription": "Aktualiséiert Metadata fir Schauspiller a Regisseuren an denger Mediebibliothéik.", + "TaskRefreshTrickplayImagesDescription": "Erstellt Trickplay-Viraussiichten fir Videoen an aktivéierte Bibliothéiken.", + "TaskCleanTranscode": "Transkodéieren botzen", + "TaskCleanTranscodeDescription": "Läscht Transkodéierungsdateien, déi méi al wéi een Dag sinn.", + "TaskRefreshChannels": "Kanäl aktualiséieren", + "TaskDownloadMissingLyrics": "Fehlend Liddertexter eroflueden", + "TaskDownloadMissingLyricsDescription": "Lued Liddertexter fir Lidder erof", + "TaskDownloadMissingSubtitles": "Fehlend Ënnertitelen eroflueden", + "TaskOptimizeDatabase": "Datebank optiméieren", + "TaskKeyframeExtractor": "Schlësselbild Extrakter", + "TaskCleanCollectionsAndPlaylists": "Sammlungen a Playlisten botzen", + "TaskCleanCollectionsAndPlaylistsDescription": "Ewechhuele vun Elementer aus Sammlungen a Playlisten, déi net méi existéieren.", + "TaskExtractMediaSegments": "Mediesegment-Scan", + "NewVersionIsAvailable": "Nei Versioun fir Jellyfin Server ass verfügbar.", + "CameraImageUploadedFrom": "En neit Kamera Bild gouf vu {0} eropgelueden", + "PluginInstalledWithName": "{0} installéiert", + "TaskMoveTrickplayImagesDescription": "Verschëfft existent Trickplay-Dateien no de Bibliothéik-Astellungen.", + "AppDeviceValues": "App: {0}, Geräter: {1}", + "FailedLoginAttemptWithUserName": "Net Gelongen Umeldung {0}", + "HeaderLiveTV": "LiveTV", + "ItemAddedWithName": "Element derbäi gesat: {0}", + "NotificationOptionServerRestartRequired": "Server Restart Erfuerderlech", + "ScheduledTaskStartedWithName": "Aufgab: {0} gestart", + "AuthenticationSucceededWithUserName": "{0} Authentifikatioun gelongen", + "MixedContent": "Gemëschten Inhalt", + "MessageApplicationUpdated": "Jellyfin Server Aktualiséiert", + "SubtitleDownloadFailureFromForItem": "Ënnertitel Download Feeler vun {0} fir {1}", + "TaskCleanLogsDescription": "Läscht Log-Dateien, déi méi al wéi {0} Deeg sinn.", + "TaskUpdatePlugins": "Plugins aktualiséieren", + "UserDeletedWithName": "Benotzer {0} geläscht", + "TasksApplicationCategory": "Applikatioun", + "TaskCleanCacheDescription": "Läscht Cache-Dateien, déi net méi vum System gebraucht ginn.", + "UserStoppedPlayingItemWithValues": "{0} ass mat {1} op {2} fäerdeg", + "TaskAudioNormalizationDescription": "Scannt Dateien no Donnéeën fir d’Audio-Normaliséierung.", + "TaskRefreshTrickplayImages": "Trickplay-Biller generéieren", + "TaskDownloadMissingSubtitlesDescription": "Sicht am Internet no fehlenden Ënnertitelen op Basis vun der Metadata-Konfiguratioun.", + "TaskMoveTrickplayImages": "Trickplay-Biller-Plaz migréieren", + "TaskUpdatePluginsDescription": "Lued Aktualiséierungen erof a installéiert se fir Plugins, déi fir automatesch Updates konfiguréiert sinn.", + "TaskKeyframeExtractorDescription": "Extrahéiert Schlësselbiller aus Videodateien, fir méi präzis HLS-Playlisten ze erstellen. Dës Aufgab kann eng längere Zäit daueren.", + "TaskRefreshChannelsDescription": "Aktualiséiert Informatiounen iwwer Internetkanäl.", + "TaskExtractMediaSegmentsDescription": "Extrahéiert oder kritt Mediesegmenter aus Plugins, déi MediaSegment ënnerstëtzen.", + "TaskOptimizeDatabaseDescription": "Kompriméiert d’Datebank a schneit de fräie Speicherplatz zou. Dës Aufgab no engem Bibliothéik-Scan oder anere Ännerungen, déi Datebankmodifikatioune mat sech bréngen, auszeféieren, kann d’Performance verbesseren." +} diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json new file mode 100644 index 00000000..bdf63b4c --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json @@ -0,0 +1,141 @@ +{ + "Albums": "Albumai", + "AppDeviceValues": "Programa: {0}, Įrenginys: {1}", + "Application": "Programėlė", + "Artists": "Atlikėjai", + "AuthenticationSucceededWithUserName": "{0} sėkmingai autentifikuota", + "Books": "Knygos", + "CameraImageUploadedFrom": "Nauja nuotrauka įkelta iš kameros {0}", + "Channels": "Kanalai", + "ChapterNameValue": "Scena{0}", + "Collections": "Rinkiniai", + "DeviceOfflineWithName": "{0} buvo atjungtas", + "DeviceOnlineWithName": "{0} prisijungęs", + "FailedLoginAttemptWithUserName": "Nesėkmingas {0} bandymas prisijungti", + "Favorites": "Mėgstami", + "Folders": "Katalogai", + "Genres": "Žanrai", + "HeaderAlbumArtists": "Albumo atlikėjai", + "HeaderContinueWatching": "Žiūrėti toliau", + "HeaderFavoriteAlbums": "Mėgstami albumai", + "HeaderFavoriteArtists": "Mėgstami atlikėjai", + "HeaderFavoriteEpisodes": "Mėgstamiausios serijos", + "HeaderFavoriteShows": "Mėgstamiausios TV Laidos", + "HeaderFavoriteSongs": "Mėgstamos Dainos", + "HeaderLiveTV": "Tiesioginė TV", + "HeaderNextUp": "Toliau", + "HeaderRecordingGroups": "Įrašų grupės", + "HomeVideos": "Namų vaizdo įrašai", + "Inherit": "Paveldėti", + "ItemAddedWithName": "{0} - buvo įkeltas į biblioteką", + "ItemRemovedWithName": "{0} - buvo pašalinta iš bibliotekos", + "LabelIpAddressValue": "IP adresas: {0}", + "LabelRunningTimeValue": "Trukmė: {0}", + "Latest": "Naujausi", + "MessageApplicationUpdated": "\"Jellyfin Server\" atnaujintas", + "MessageApplicationUpdatedTo": "\"Jellyfin Server\" buvo atnaujinta iki {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Serverio nustatymai (skyrius {0}) buvo atnaujinti", + "MessageServerConfigurationUpdated": "Serverio nustatymai buvo atnaujinti", + "MixedContent": "Mišrus turinys", + "Movies": "Filmai", + "Music": "Muzika", + "MusicVideos": "Muzikiniai vaizdo įrašai", + "NameInstallFailed": "{0} diegimo klaida", + "NameSeasonNumber": "Sezonas {0}", + "NameSeasonUnknown": "Sezonas neatpažintas", + "NewVersionIsAvailable": "Nauja \"Jellyfin Server\" versija yra prieinama atsisiuntimui.", + "NotificationOptionApplicationUpdateAvailable": "Galimi programos atnaujinimai", + "NotificationOptionApplicationUpdateInstalled": "Programos atnaujinimai įdiegti", + "NotificationOptionAudioPlayback": "Garso atkūrimas pradėtas", + "NotificationOptionAudioPlaybackStopped": "Garso atkūrimas sustabdytas", + "NotificationOptionCameraImageUploaded": "Kameros vaizdai įkelti", + "NotificationOptionInstallationFailed": "Diegimo klaida", + "NotificationOptionNewLibraryContent": "Naujas turinys įkeltas", + "NotificationOptionPluginError": "Įskiepio klaida", + "NotificationOptionPluginInstalled": "Įskiepis įdiegtas", + "NotificationOptionPluginUninstalled": "Įskiepis išdiegtas", + "NotificationOptionPluginUpdateInstalled": "Įskiepio atnaujinimas įdiegtas", + "NotificationOptionServerRestartRequired": "Reikalingas serverio perleidimas", + "NotificationOptionTaskFailed": "Suplanuotos užduoties klaida", + "NotificationOptionUserLockedOut": "Naudotojas užblokuotas", + "NotificationOptionVideoPlayback": "Vaizdo įrašo atkūrimas pradėtas", + "NotificationOptionVideoPlaybackStopped": "Vaizdo įrašo atkūrimas sustabdytas", + "Photos": "Nuotraukos", + "Playlists": "Grojaraščiai", + "Plugin": "Įskiepis", + "PluginInstalledWithName": "{0} buvo įdiegtas", + "PluginUninstalledWithName": "{0} buvo pašalintas", + "PluginUpdatedWithName": "{0} buvo atnaujintas", + "ProviderValue": "Paslaugos tiekėjas: {0}", + "ScheduledTaskFailedWithName": "{0} nepavyko", + "ScheduledTaskStartedWithName": "{0} paleista", + "ServerNameNeedsToBeRestarted": "{0} reikia iš naujo paleisti", + "Shows": "Laidos", + "Songs": "Kūriniai", + "StartupEmbyServerIsLoading": "Jellyfin Server kraunasi. Netrukus pabandykite dar kartą.", + "SubtitleDownloadFailureFromForItem": "{1} subtitrai buvo nesėkmingai parsiųsti iš {0}", + "Sync": "Sinchronizuoti", + "System": "Sistema", + "TvShows": "TV laidos", + "User": "Naudotojas", + "UserCreatedWithName": "Buvo sukurtas {0} naudotojas", + "UserDeletedWithName": "Naudotojas {0} ištrintas", + "UserDownloadingItemWithValues": "{0} siunčiasi {1}", + "UserLockedOutWithName": "Naudotojas {0} užblokuotas", + "UserOfflineFromDevice": "{0} buvo atjungtas nuo {1}", + "UserOnlineFromDevice": "{0} prisijungęs iš {1}", + "UserPasswordChangedWithName": "Slaptažodis pakeistas naudotojui {0}", + "UserPolicyUpdatedWithName": "Naudotojo {0} teisės buvo pakeistos", + "UserStartedPlayingItemWithValues": "{0} leidžia {1} į {2}", + "UserStoppedPlayingItemWithValues": "{0} baigė leisti {1} į {2}", + "ValueHasBeenAddedToLibrary": "{0} pridėtas į mediateką", + "ValueSpecialEpisodeName": "Ypatingų - {0}", + "VersionNumber": "Versija {0}", + "TaskUpdatePluginsDescription": "Atsisiunčia ir įdiegia įskiepių, kurie sukonfigūruoti atnaujinti automatiškai, naujinius.", + "TaskUpdatePlugins": "Atnaujinti įskieius", + "TaskDownloadMissingSubtitlesDescription": "Ieško trūkstamų subtitrų internete remiantis metaduomenų konfigūracija.", + "TaskCleanTranscodeDescription": "Ištrina dienos senumo perkodavimo failus.", + "TaskCleanTranscode": "Išvalyti perkodavimo katalogą", + "TaskRefreshLibraryDescription": "Skenuoja medijos biblioteką, ieškodamas naujų failų, ir atnaujina metaduomenis.", + "TaskRefreshLibrary": "Skenuoti medijos biblioteką", + "TaskDownloadMissingSubtitles": "Atsisiųsti trūkstamus subtitrus", + "TaskRefreshChannelsDescription": "Atnaujina internetinių kanalų informaciją.", + "TaskRefreshChannels": "Atnaujinti kanalus", + "TaskRefreshPeopleDescription": "Atnaujina metaduomenis apie aktorius ir režisierius jūsų medijos bibliotekoje.", + "TaskRefreshPeople": "Atnaujinti žmones", + "TaskCleanLogsDescription": "Ištrina žurnalo failus kurie yra senesni nei {0} dienos.", + "TaskCleanLogs": "Išvalyti žurnalą", + "TaskRefreshChapterImagesDescription": "Sukuria vaizdo įrašų, kuriuose yra skyrių, miniatiūras.", + "TaskRefreshChapterImages": "Ištraukti skyrių vaizdus", + "TaskCleanCache": "Išvalyti talpyklą", + "TaskCleanCacheDescription": "Ištrina talpyklos failus, kurių daugiau nereikia sistemai.", + "TasksChannelsCategory": "Internetiniai kanalai", + "TasksApplicationCategory": "Programa", + "TasksLibraryCategory": "Biblioteka", + "TasksMaintenanceCategory": "Priežiūra", + "TaskCleanActivityLog": "Išvalyti veiklos žurnalą", + "Undefined": "Neapibrėžtas", + "Forced": "Priverstinis", + "Default": "Numatytas", + "TaskCleanActivityLogDescription": "Ištrina senesnius nei nustatytas amžius veiklos žurnalo įrašus.", + "TaskOptimizeDatabase": "Optimizuoti duomenų bazę", + "TaskKeyframeExtractorDescription": "Iš vaizdo įrašo paruošia reikšminius kadrus, kad būtų sukuriamas tikslenis HLS grojaraštis. Šios užduoties vykdymas gali ilgai užtrukti.", + "TaskKeyframeExtractor": "Reikšminių kadrų (KeyFrame) išgavėjas", + "TaskOptimizeDatabaseDescription": "Suspaudžia duomenų bazę ir atlaisvina vietą. Paleidžiant šią užduotį, po bibliotekos skenavimo arba kitų veiksmų kurie galimai modifikuoja duomenų bazę, gali pagerinti greitaveiką.", + "External": "Išorinis", + "HearingImpaired": "Su klausos sutrikimais", + "TaskRefreshTrickplayImages": "Generuoti Trickplay atvaizdus", + "TaskRefreshTrickplayImagesDescription": "Sukuria trickplay peržiūras vaizdo įrašams įgalintose bibliotekose.", + "TaskCleanCollectionsAndPlaylists": "Išvalo duomenis rinkiniuose ir grojaraščiuose", + "TaskCleanCollectionsAndPlaylistsDescription": "Pašalina neegzistuojančius elementus iš rinkinių ir grojaraščių.", + "TaskAudioNormalization": "Garso normalizavimas", + "TaskAudioNormalizationDescription": "Skenuoja failus, ieškant garso normalizavimo duomenų.", + "TaskExtractMediaSegments": "Medijos segmentų nuskaitymas", + "TaskDownloadMissingLyrics": "Parsisiųsti trūkstamus dainų tekstus", + "TaskExtractMediaSegmentsDescription": "Ištraukia arba gauna medijos segmentus iš MediaSegment ijungtų įskiepių.", + "TaskMoveTrickplayImages": "Pakeisti Trickplay vaizdų vietą", + "TaskMoveTrickplayImagesDescription": "Perkelia egzistuojančius trickplay failus pagal bibliotekos nustatymus.", + "TaskDownloadMissingLyricsDescription": "Parsisiųsti dainų žodžius", + "CleanupUserDataTask": "Naudotojo duomenų valymo užduotis", + "CleanupUserDataTaskDescription": "Iš medijos, kurios nebėra bent 90 dienų, išvalo visus naudotojo duomenis (žiūrėjimo būseną, mėgstamiausią būseną ir t. t.)." +} diff --git a/Emby.Server.Implementations/Localization/Core/lv.json b/Emby.Server.Implementations/Localization/Core/lv.json new file mode 100644 index 00000000..55549c66 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/lv.json @@ -0,0 +1,141 @@ +{ + "ServerNameNeedsToBeRestarted": "{0} ir vajadzīgs restarts", + "NotificationOptionTaskFailed": "Plānota uzdevuma kļūme", + "HeaderRecordingGroups": "Ierakstu grupas", + "UserPolicyUpdatedWithName": "Lietotāju politika atjaunota priekš {0}", + "SubtitleDownloadFailureFromForItem": "Subtitru lejupielāde no {0} priekš {1} neizdevās", + "NotificationOptionVideoPlaybackStopped": "Video atskaņošana apturēta", + "NotificationOptionVideoPlayback": "Video atskaņošana sākta", + "NotificationOptionInstallationFailed": "Instalācija neizdevās", + "AuthenticationSucceededWithUserName": "{0} veiksmīgi autentificējies", + "ValueSpecialEpisodeName": "Speciālais - {0}", + "ScheduledTaskStartedWithName": "{0} iesākts", + "ScheduledTaskFailedWithName": "{0} neizdevās", + "Photos": "Attēli", + "NotificationOptionUserLockedOut": "Lietotājs bloķēts", + "LabelRunningTimeValue": "Garums: {0}", + "Inherit": "Pārmantot", + "AppDeviceValues": "Lietotne: {0}, Ierīce: {1}", + "VersionNumber": "Versija {0}", + "ValueHasBeenAddedToLibrary": "{0} tika pievienots jūsu multvides bibliotēkai", + "UserStoppedPlayingItemWithValues": "{0} ir beidzis atskaņot {1} uz {2}", + "UserStartedPlayingItemWithValues": "{0} atskaņo {1} uz {2}", + "UserPasswordChangedWithName": "Lietotāja {0} parole tika nomainīta", + "UserOnlineFromDevice": "{0} ir tiešsaistē no {1}", + "UserOfflineFromDevice": "{0} ir atvienojies no {1}", + "UserLockedOutWithName": "Lietotājs {0} ir ticis bloķēts", + "UserDownloadingItemWithValues": "{0} lejupielādē {1}", + "UserDeletedWithName": "Lietotājs {0} ir izdzēsts", + "UserCreatedWithName": "Lietotājs {0} ir ticis izveidots", + "User": "Lietotājs", + "TvShows": "TV raidījumi", + "Sync": "Sinhronizācija", + "System": "Sistēma", + "StartupEmbyServerIsLoading": "Jellyfin Serveris lādējas. Lūdzu mēģiniet vēlreiz pēc brīža.", + "Songs": "Dziesmas", + "Shows": "Šovi", + "PluginUpdatedWithName": "{0} tika atjaunots", + "PluginUninstalledWithName": "{0} tika noņemts", + "PluginInstalledWithName": "{0} tika uzstādīts", + "Plugin": "Paplašinājums", + "Playlists": "Atskaņošanas saraksti", + "MixedContent": "Jaukts saturs", + "HomeVideos": "Mājas video", + "HeaderNextUp": "Nākamais", + "ChapterNameValue": "{0}. nodaļa", + "Application": "Lietotne", + "NotificationOptionServerRestartRequired": "Nepieciešams servera restarts", + "NotificationOptionPluginUpdateInstalled": "Paplašinājuma atjauninājums uzstādīts", + "NotificationOptionPluginUninstalled": "Paplašinājums noņemts", + "NotificationOptionPluginInstalled": "Paplašinājums uzstādīts", + "NotificationOptionPluginError": "Paplašinājuma kļūda", + "NotificationOptionNewLibraryContent": "Jauns saturs pievienots", + "NotificationOptionCameraImageUploaded": "Kameras attēls augšupielādēts", + "NotificationOptionAudioPlaybackStopped": "Audio atskaņošana apturēta", + "NotificationOptionAudioPlayback": "Audio atskaņošana sākta", + "NotificationOptionApplicationUpdateInstalled": "Lietotnes atjauninājums uzstādīts", + "NotificationOptionApplicationUpdateAvailable": "Lietotnes atjauninājums pieejams", + "NewVersionIsAvailable": "Lejupielādei ir pieejama jauna Jellyfin Server versija.", + "NameSeasonUnknown": "Nezināma sezona", + "NameSeasonNumber": "{0}. sezona", + "NameInstallFailed": "{0} instalācija neizdevās", + "MusicVideos": "Mūzikas video", + "Music": "Mūzika", + "Movies": "Filmas", + "MessageServerConfigurationUpdated": "Servera konfigurācija ir tikusi atjaunota", + "MessageNamedServerConfigurationUpdatedWithValue": "Servera konfigurācijas sadaļa {0} tika atjaunota", + "MessageApplicationUpdatedTo": "Jellyfin Server ir ticis atjaunots uz {0}", + "MessageApplicationUpdated": "Jellyfin Server ir ticis atjaunots", + "Latest": "Jaunākais", + "LabelIpAddressValue": "IP adrese: {0}", + "ItemRemovedWithName": "{0} tika noņemts no bibliotēkas", + "ItemAddedWithName": "{0} tika pievienots bibliotēkai", + "HeaderLiveTV": "Tiešraides TV", + "HeaderContinueWatching": "Turpini skatīties", + "HeaderAlbumArtists": "Albumu izpildītāji", + "Genres": "Žanri", + "Folders": "Mapes", + "Favorites": "Izlase", + "FailedLoginAttemptWithUserName": "Neizdevies ieiešanas mēģinājums no {0}", + "DeviceOnlineWithName": "Savienojums ar {0} ir izveidots", + "DeviceOfflineWithName": "Savienojums ar {0} ir pārtraukts", + "Collections": "Kolekcijas", + "Channels": "Kanāli", + "CameraImageUploadedFrom": "Jauns kameras attēls tika augšupielādēts no {0}", + "Books": "Grāmatas", + "Artists": "Izpildītāji", + "Albums": "Albumi", + "ProviderValue": "Provider: {0}", + "HeaderFavoriteSongs": "Dziesmu izlase", + "HeaderFavoriteShows": "Raidījumu izlase", + "HeaderFavoriteEpisodes": "Sēriju izlase", + "HeaderFavoriteArtists": "Izpildītāju izlase", + "HeaderFavoriteAlbums": "Albumu izlase", + "TaskCleanCacheDescription": "Nodzēš kešatmiņas datnes, kas vairs nav sistēmai vajadzīgas.", + "TaskRefreshChapterImages": "Izvilkt nodaļu attēlus", + "TasksApplicationCategory": "Lietotne", + "TasksLibraryCategory": "Bibliotēka", + "TaskDownloadMissingSubtitlesDescription": "Meklē internetā trūkstošos subtitrus, pamatojoties uz metadatu konfigurāciju.", + "TaskDownloadMissingSubtitles": "Lejupielādēt trūkstošos subtitrus", + "TaskRefreshChannelsDescription": "Atjauno interneta kanālu informāciju.", + "TaskRefreshChannels": "Atjaunot kanālus", + "TaskCleanTranscodeDescription": "Izdzēš transkodēšanas datnes, kas ir senākas par vienu dienu.", + "TaskCleanTranscode": "Iztīrīt transkodēšanas mapi", + "TaskUpdatePluginsDescription": "Lejupielādē un uzstāda atjauninājumus paplašinājumiem, kam ir uzstādīta automātiskā atjaunināšana.", + "TaskUpdatePlugins": "Atjaunot paplašinājumus", + "TaskRefreshPeopleDescription": "Atjauno metadatus aktieriem un direktoriem jūsu multivides bibliotēkā.", + "TaskRefreshPeople": "Atjaunot cilvēkus", + "TaskCleanLogsDescription": "Nodzēš žurnāla ierakstus, kas ir senāki par {0} dienām.", + "TaskCleanLogs": "Iztīrīt žurnālu mapi", + "TaskRefreshLibraryDescription": "Skenē jūsu multivides bibliotēku, lai atrastu jaunas datnes, un atsvaidzina metadatus.", + "TaskRefreshLibrary": "Skenēt multivides bibliotēku", + "TaskRefreshChapterImagesDescription": "Izveido sīktēlus priekš video ar sadaļām.", + "TaskCleanCache": "Iztīrīt kešatmiņas mapi", + "TasksChannelsCategory": "Interneta kanāli", + "TasksMaintenanceCategory": "Apkope", + "Forced": "Piespiedu", + "TaskCleanActivityLogDescription": "Nodzēš darbību žurnāla ierakstus, kuri ir vecāki par doto vecumu.", + "TaskCleanActivityLog": "Notīrīt darbību žurnālu", + "Undefined": "Nenoteikts", + "Default": "Noklusējuma", + "TaskOptimizeDatabaseDescription": "Saspiež datubāzi un atbrīvo atmiņu. Šī uzdevuma palaišana pēc bibliotēku skenēšanas vai citām, ar datubāzi saistītām, izmaiņām iespējams uzlabos ātrdarbību.", + "TaskOptimizeDatabase": "Optimizēt datubāzi", + "External": "Ārējais", + "HearingImpaired": "Ar dzirdes traucējumiem", + "TaskKeyframeExtractor": "Atslēgkadru ekstraktors", + "TaskKeyframeExtractorDescription": "Izvelk atslēgkadrus no video failiem lai izveidotu precīzākus HLS atskaņošanas sarakstus. Šis process var būt ilgs.", + "TaskRefreshTrickplayImages": "Ģenerēt partīšanas attēlus", + "TaskRefreshTrickplayImagesDescription": "Izveido priekšskatījumus videoklipu pārtīšanai iespējotajās bibliotēkās.", + "TaskAudioNormalization": "Audio normalizācija", + "TaskCleanCollectionsAndPlaylistsDescription": "Noņem vairs neeksistējošus vienumus no kolekcijām un atskaņošanas sarakstiem.", + "TaskAudioNormalizationDescription": "Skanē failus priekš audio normālizācijas informācijas.", + "TaskCleanCollectionsAndPlaylists": "Notīrīt kolekcijas un atskaņošanas sarakstus", + "TaskExtractMediaSegments": "Multivides segmenta skenēšana", + "TaskExtractMediaSegmentsDescription": "Izvelk vai iegūst multivides segmentus no MediaSegment iespējotiem spraudņiem.", + "TaskMoveTrickplayImages": "Trickplay attēlu pārvietošana", + "TaskMoveTrickplayImagesDescription": "Pārvieto esošos trickplay failus atbilstoši bibliotēkas iestatījumiem.", + "TaskDownloadMissingLyrics": "Lejupielādēt trūkstošos vārdus", + "TaskDownloadMissingLyricsDescription": "Lejupielādēt vārdus dziesmām", + "CleanupUserDataTask": "Lietotāju datu tīrīšanas uzdevums", + "CleanupUserDataTaskDescription": "Notīra visus lietotāja datus (skatīšanās stāvokļus, favorītu statusi utt.) no medijiem, kas vairs nav pieejami vismaz 90 dienas." +} diff --git a/Emby.Server.Implementations/Localization/Core/lzh.json b/Emby.Server.Implementations/Localization/Core/lzh.json new file mode 100644 index 00000000..9fb53e41 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/lzh.json @@ -0,0 +1,11 @@ +{ + "Albums": "辑册", + "Artists": "艺人", + "AuthenticationSucceededWithUserName": "{0} 授之权矣", + "Books": "册", + "Genres": "类", + "HeaderAlbumArtists": "辑者", + "Favorites": "至爱", + "Folders": "箧", + "HeaderContinueWatching": "接目未竟" +} diff --git a/Emby.Server.Implementations/Localization/Core/mi.json b/Emby.Server.Implementations/Localization/Core/mi.json new file mode 100644 index 00000000..3b20abb3 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/mi.json @@ -0,0 +1,9 @@ +{ + "Albums": "Pukaemi", + "AppDeviceValues": "Taupānga: {0}, Pūrere: {1}", + "Application": "Taupānga", + "Artists": "Kaiwaiata", + "AuthenticationSucceededWithUserName": "{0} has been successfully authenticated", + "Books": "Ngā pukapuka", + "CameraImageUploadedFrom": "Kua tuku ake he whakaahua kāmera hou mai i {0}" +} diff --git a/Emby.Server.Implementations/Localization/Core/mk.json b/Emby.Server.Implementations/Localization/Core/mk.json new file mode 100644 index 00000000..6da31227 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/mk.json @@ -0,0 +1,136 @@ +{ + "ScheduledTaskFailedWithName": "{0} неуспешно", + "ProviderValue": "Провајдер: {0}", + "PluginUpdatedWithName": "{0} беше надоградено", + "PluginUninstalledWithName": "{0} беше успешно деинсталирано", + "PluginInstalledWithName": "{0} беше успешно инсталирано", + "Plugin": "Додатоци", + "Playlists": "Плејлисти", + "Photos": "Слики", + "NotificationOptionVideoPlaybackStopped": "Видео стопирано", + "NotificationOptionVideoPlayback": "Видео пуштено", + "NotificationOptionUserLockedOut": "Корисникот е ослободен", + "NotificationOptionTaskFailed": "Закажани задачи неуспешно", + "NotificationOptionServerRestartRequired": "Задолжително рестартирање на серверот", + "NotificationOptionPluginUpdateInstalled": "Надоградба на Додаток успешна", + "NotificationOptionPluginUninstalled": "Додаток успешно деинсталиран", + "NotificationOptionPluginInstalled": "Додаток успешно инсталиран", + "NotificationOptionPluginError": "Грешка на додаток", + "NotificationOptionNewLibraryContent": "Додадена нова содржина", + "NotificationOptionInstallationFailed": "Неуспешна Инсталација", + "NotificationOptionCameraImageUploaded": "Слика од камера поставена", + "NotificationOptionAudioPlaybackStopped": "Аудио стопирано", + "NotificationOptionAudioPlayback": "Аудио стартувано", + "NotificationOptionApplicationUpdateInstalled": "Надоградбата на Апликацијата е иснталирана", + "NotificationOptionApplicationUpdateAvailable": "Возможна надоградба на Апликацијата", + "NewVersionIsAvailable": "Нова верзија од Jellyfin е возможна за спуштање.", + "NameSeasonUnknown": "Непозната Сезона", + "NameSeasonNumber": "Сезона {0}", + "NameInstallFailed": "{0} неуспешна инсталација", + "MusicVideos": "Музички видеа", + "Music": "Музика", + "Movies": "Филмови", + "MixedContent": "Мешана содржина", + "MessageServerConfigurationUpdated": "Серверската конфигурација беше надградена", + "MessageNamedServerConfigurationUpdatedWithValue": "Секцијата на конфигурација на сервер {0} беше надоградена", + "MessageApplicationUpdatedTo": "Jellyfin беше надограден до {0}", + "MessageApplicationUpdated": "Jellyfin Серверот беше надограден", + "Latest": "Последно", + "LabelRunningTimeValue": "Време на работа: {0}", + "LabelIpAddressValue": "ИП Адреса: {0}", + "ItemRemovedWithName": "{0} е избришано до Библиотеката", + "ItemAddedWithName": "{0} беше додадено во Библиотеката", + "Inherit": "Следно", + "HomeVideos": "Домашни Видеа", + "HeaderRecordingGroups": "Групи на снимање", + "HeaderNextUp": "Следно", + "HeaderLiveTV": "ТВ", + "HeaderFavoriteSongs": "Омилени Песни", + "HeaderFavoriteShows": "Омилени Серии", + "HeaderFavoriteEpisodes": "Омилени Епизоди", + "HeaderFavoriteArtists": "Омилени Изведувачи", + "HeaderFavoriteAlbums": "Омилени Албуми", + "HeaderContinueWatching": "Продолжи со Гледање", + "HeaderAlbumArtists": "Изведувачи од Албуми", + "Genres": "Жанрови", + "Folders": "Папки", + "Favorites": "Омилени", + "FailedLoginAttemptWithUserName": "Неуспешен обид за најавување од {0}", + "DeviceOnlineWithName": "{0} е приклучен", + "DeviceOfflineWithName": "{0} се исклучи", + "Collections": "Колекции", + "ChapterNameValue": "Дел {0}", + "Channels": "Канали", + "CameraImageUploadedFrom": "Нова слика од камера беше поставена од {0}", + "Books": "Книги", + "AuthenticationSucceededWithUserName": "{0} успешно поврзан", + "Artists": "Изведувачи", + "Application": "Апликација", + "AppDeviceValues": "Апликација: {0}, Уред: {1}", + "Albums": "Албуми", + "VersionNumber": "Верзија {0}", + "ValueSpecialEpisodeName": "Специјално - {0}", + "ValueHasBeenAddedToLibrary": "{0} е додадено во твојата библиотека", + "UserStoppedPlayingItemWithValues": "{0} заврши со репродукција {1} во {2}", + "UserStartedPlayingItemWithValues": "{0} пушти {1} на {2}", + "UserPolicyUpdatedWithName": "Полисата на користење беше надоградена за {0}", + "UserPasswordChangedWithName": "Лозинката е сменета за корисникот {0}", + "UserOnlineFromDevice": "{0} е приклучен од {1}", + "UserOfflineFromDevice": "{0} е дисконектиран од {1}", + "UserLockedOutWithName": "Корисникот {0} е заклучен", + "UserDownloadingItemWithValues": "{0} се спушта {1}", + "UserDeletedWithName": "Корисникот {0} е избришан", + "UserCreatedWithName": "Корисникот {0} е креиран", + "User": "Корисник", + "TvShows": "ТВ Серии", + "System": "Систем", + "Sync": "Синхронизација", + "SubtitleDownloadFailureFromForItem": "Преводот неуспешно се спушти од {0} за {1}", + "StartupEmbyServerIsLoading": "Jellyfin Server се пушта. Ве молиме причекајте.", + "Songs": "Песни", + "Shows": "Серии", + "ServerNameNeedsToBeRestarted": "{0} треба да се рестартира", + "ScheduledTaskStartedWithName": "{0} започна", + "TaskRefreshChapterImages": "Извези Слики од Поглавје", + "TaskCleanCacheDescription": "Ги брише кешираните фајлови што не се повеќе потребни од системот.", + "TaskCleanCache": "Исчисти Го Кешот", + "TasksChannelsCategory": "Интернет Канали", + "TasksApplicationCategory": "Апликација", + "TasksLibraryCategory": "Библиотека", + "TasksMaintenanceCategory": "Одржување", + "Undefined": "Недефинирано", + "Forced": "Принудно", + "Default": "Зададено", + "TaskKeyframeExtractorDescription": "Извлекува клучни рамки од видео фајлови за да се направат попрецизни HLS плејлисти. Оваа задача може да работи многу долго време.", + "TaskKeyframeExtractor": "Извлекувач на клучни рамки", + "TaskOptimizeDatabaseDescription": "Компактира датабазата и смалува празното место. Извршувањето на оваа задача по скенирање на библиотеката или правење други промени што прават модификации на датабазата може да подобри перформансите.", + "TaskOptimizeDatabase": "Оптимизирај датабаза", + "TaskDownloadMissingSubtitlesDescription": "Пребарува интернет за преводи што недостиваат според метадата конфигурација.", + "TaskDownloadMissingSubtitles": "Симни преводи што недостигаат", + "TaskRefreshChannelsDescription": "Ажурирај информации за интернет канали.", + "TaskRefreshChannels": "Ажурирај Канали", + "TaskCleanTranscodeDescription": "Избриши транскодирани фајлови постари од еден ден.", + "TaskCleanTranscode": "Исчисти Директориум за Транскодирање", + "TaskUpdatePluginsDescription": "Симни и инсталирај ажурирања за плагини што се конфигурирани за автоматско ажурирање.", + "TaskUpdatePlugins": "Ажурирај Плагини", + "TaskRefreshPeopleDescription": "Ажурирај метадата за акери и директори во вашата медиска библиотека.", + "TaskRefreshPeople": "Ажурирајте ги Луѓето", + "TaskCleanLogsDescription": "Избриши лог фајлови постари од {0} денови.", + "TaskCleanLogs": "Избриши Директориум на Логови", + "TaskRefreshLibraryDescription": "Скенирајте ја вашата медиска библиотека за нови фајлови и ажурирај метадата.", + "TaskRefreshLibrary": "Скенирај Медиумска Библиотека", + "TaskRefreshChapterImagesDescription": "Создава тамбнеил за видеата шти имаат поглавја.", + "TaskCleanActivityLogDescription": "Избришува логови на активности постари од определеното време.", + "TaskCleanActivityLog": "Избриши Лог на Активности", + "External": "Надворешен", + "HearingImpaired": "Оштетен слух", + "TaskCleanCollectionsAndPlaylists": "Исчисти ги колекциите и плејлистите", + "TaskAudioNormalizationDescription": "Скенирање датотеки за податоци за нормализација на звукот.", + "TaskDownloadMissingLyrics": "Преземи стихови кои недостасуваат", + "TaskDownloadMissingLyricsDescription": "Преземи стихови/текстови за песни", + "TaskRefreshTrickplayImages": "Генерирај слики за прегледување (Trickplay)", + "TaskAudioNormalization": "Нормализација на звукот", + "TaskRefreshTrickplayImagesDescription": "Креира трикплеј прегледи за видеа во овозможените библиотеки.", + "TaskCleanCollectionsAndPlaylistsDescription": "Отстранува ставки од колекциите и плејлистите што веќе не постојат.", + "TaskExtractMediaSegments": "Скенирање на сегменти на содржина" +} diff --git a/Emby.Server.Implementations/Localization/Core/ml.json b/Emby.Server.Implementations/Localization/Core/ml.json new file mode 100644 index 00000000..8c20ded3 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ml.json @@ -0,0 +1,133 @@ +{ + "AppDeviceValues": "അപ്ലിക്കേഷൻ: {0}, ഉപകരണം: {1}", + "Application": "അപ്ലിക്കേഷൻ", + "AuthenticationSucceededWithUserName": "{0} വിജയകരമായി പ്രാമാണീകരിച്ചു", + "CameraImageUploadedFrom": "{0} എന്നതിൽ നിന്ന് ഒരു പുതിയ ക്യാമറ ചിത്രം അപ്‌ലോഡുചെയ്‌തു", + "ChapterNameValue": "അധ്യായം {0}", + "DeviceOfflineWithName": "{0} വിച്ഛേദിച്ചു", + "DeviceOnlineWithName": "{0} ബന്ധിപ്പിച്ചു", + "FailedLoginAttemptWithUserName": "{0}ൽ നിന്നുള്ള പ്രവേശന ശ്രമം പരാജയപ്പെട്ടു", + "Forced": "നിർബന്ധിതമായി", + "HeaderFavoriteAlbums": "പ്രിയപ്പെട്ട ആൽബങ്ങൾ", + "HeaderFavoriteArtists": "പ്രിയപ്പെട്ട കലാകാരന്മാർ", + "HeaderFavoriteEpisodes": "പ്രിയപ്പെട്ട എപ്പിസോഡുകൾ", + "HeaderFavoriteShows": "പ്രിയപ്പെട്ട ഷോകൾ", + "HeaderFavoriteSongs": "പ്രിയപ്പെട്ട ഗാനങ്ങൾ", + "HeaderLiveTV": "തത്സമയ ടിവി", + "HeaderNextUp": "അടുത്തത്", + "HeaderRecordingGroups": "ഗ്രൂപ്പുകൾ റെക്കോർഡുചെയ്യുന്നു", + "HomeVideos": "ഹോം വീഡിയോകൾ", + "Inherit": "അനന്തരാവകാശം", + "ItemAddedWithName": "{0} ലൈബ്രറിയിൽ ചേർത്തു", + "ItemRemovedWithName": "{0} ലൈബ്രറിയിൽ നിന്ന് നീക്കംചെയ്തു", + "LabelIpAddressValue": "IP വിലാസം: {0}", + "LabelRunningTimeValue": "പ്രവർത്തന സമയം: {0}", + "Latest": "ഏറ്റവും പുതിയ", + "MessageApplicationUpdated": "ജെല്ലിഫിൻ സെർവർ അപ്‌ഡേറ്റുചെയ്‌തു", + "MessageApplicationUpdatedTo": "ജെല്ലിഫിൻ സെർവർ {0 to ലേക്ക് അപ്‌ഡേറ്റുചെയ്‌തു", + "MessageNamedServerConfigurationUpdatedWithValue": "സെർവർ കോൺഫിഗറേഷൻ വിഭാഗം {0 അപ്‌ഡേറ്റുചെയ്‌തു", + "MessageServerConfigurationUpdated": "സെർവർ കോൺഫിഗറേഷൻ അപ്‌ഡേറ്റുചെയ്‌തു", + "MixedContent": "മിശ്രിത ഉള്ളടക്കം", + "Music": "സംഗീതം", + "MusicVideos": "സംഗീത വീഡിയോകൾ", + "NameInstallFailed": "{0} ഇൻസ്റ്റാളേഷൻ പരാജയപ്പെട്ടു", + "NameSeasonNumber": "സീസൺ {0}", + "NameSeasonUnknown": "സീസൺ അജ്ഞാതം", + "NewVersionIsAvailable": "ജെല്ലിഫിൻ സെർവറിന്റെ പുതിയ പതിപ്പ് ഡ .ൺ‌ലോഡിനായി ലഭ്യമാണ്.", + "NotificationOptionApplicationUpdateAvailable": "അപ്ലിക്കേഷൻ അപ്‌ഡേറ്റ് ലഭ്യമാണ്", + "NotificationOptionApplicationUpdateInstalled": "അപ്ലിക്കേഷൻ അപ്‌ഡേറ്റ് ഇൻസ്റ്റാളുചെയ്‌തു", + "NotificationOptionAudioPlayback": "ഓഡിയോ പ്ലേബാക്ക് ആരംഭിച്ചു", + "NotificationOptionAudioPlaybackStopped": "ഓഡിയോ പ്ലേബാക്ക് നിർത്തി", + "NotificationOptionCameraImageUploaded": "ക്യാമറ ചിത്രം അപ്‌ലോഡുചെയ്‌തു", + "NotificationOptionInstallationFailed": "ഇൻസ്റ്റാളേഷൻ പരാജയം", + "NotificationOptionNewLibraryContent": "പുതിയ ഉള്ളടക്കം ചേർത്തു", + "NotificationOptionPluginError": "പ്ലഗിൻ പരാജയം", + "NotificationOptionPluginInstalled": "പ്ലഗിൻ ഇൻസ്റ്റാളുചെയ്‌തു", + "NotificationOptionPluginUninstalled": "പ്ലഗിൻ അൺഇൻസ്റ്റാൾ ചെയ്തു", + "NotificationOptionPluginUpdateInstalled": "പ്ലഗിൻ അപ്‌ഡേറ്റ് ഇൻസ്റ്റാളുചെയ്‌തു", + "NotificationOptionServerRestartRequired": "സെർവർ പുനരാരംഭിക്കൽ ആവശ്യമാണ്", + "NotificationOptionTaskFailed": "ഷെഡ്യൂൾ ചെയ്ത ടാസ്‌ക് പരാജയം", + "NotificationOptionUserLockedOut": "ഉപയോക്താവ് ലോക്ക് out ട്ട് ചെയ്‌തു", + "NotificationOptionVideoPlayback": "വീഡിയോ പ്ലേബാക്ക് ആരംഭിച്ചു", + "NotificationOptionVideoPlaybackStopped": "വീഡിയോ പ്ലേബാക്ക് നിർത്തി", + "Plugin": "പ്ലഗിൻ", + "PluginInstalledWithName": "{0} ഇൻസ്റ്റാളുചെയ്‌തു", + "PluginUninstalledWithName": "{0 un അൺഇൻസ്റ്റാൾ ചെയ്തു", + "PluginUpdatedWithName": "{0} അപ്‌ഡേറ്റുചെയ്‌തു", + "ProviderValue": "ദാതാവ്: {0}", + "ScheduledTaskFailedWithName": "{0} പരാജയപ്പെട്ടു", + "ScheduledTaskStartedWithName": "{0} ആരംഭിച്ചു", + "ServerNameNeedsToBeRestarted": "{0} പുനരാരംഭിക്കേണ്ടതുണ്ട്", + "StartupEmbyServerIsLoading": "ജെല്ലിഫിൻ സെർവർ ലോഡുചെയ്യുന്നു. ഉടൻ തന്നെ വീണ്ടും ശ്രമിക്കുക.", + "SubtitleDownloadFailureFromForItem": "സബ്ടൈറ്റിലുകൾ {1} ന് {0 from ൽ നിന്ന് ഡ download ൺലോഡ് ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", + "System": "സിസ്റ്റം", + "TvShows": "ടിവി ഷോകൾ", + "Undefined": "നിർവചിച്ചിട്ടില്ല", + "User": "ഉപയോക്താവ്", + "UserCreatedWithName": "ഉപയോക്താവ് {0 created സൃഷ്ടിച്ചു", + "UserDeletedWithName": "ഉപയോക്താവ് {0 deleted ഇല്ലാതാക്കി", + "UserDownloadingItemWithValues": "{0} ഡൗൺലോഡുചെയ്യുന്നു {1}", + "UserLockedOutWithName": "{0} ഉപയോക്താവ് ലോക്ക് out ട്ട് ചെയ്‌തു", + "UserOfflineFromDevice": "{0} {1} ൽ നിന്ന് വിച്ഛേദിച്ചു", + "UserOnlineFromDevice": "{0} {1} മുതൽ ഓൺ‌ലൈനിലാണ്", + "UserPasswordChangedWithName": "{0} ഉപയോക്താവിനായി പാസ്‌വേഡ് മാറ്റി", + "UserPolicyUpdatedWithName": "{0} എന്നതിനായി ഉപയോക്തൃ നയം അപ്‌ഡേറ്റുചെയ്‌തു", + "UserStartedPlayingItemWithValues": "{0} {2} ൽ {1} പ്ലേ ചെയ്യുന്നു", + "UserStoppedPlayingItemWithValues": "{0} {2} ൽ {1 play കളിക്കുന്നത് പൂർത്തിയാക്കി", + "ValueHasBeenAddedToLibrary": "Media 0 your നിങ്ങളുടെ മീഡിയ ലൈബ്രറിയിലേക്ക് ചേർത്തു", + "VersionNumber": "പതിപ്പ് {0}", + "TasksMaintenanceCategory": "പരിപാലനം", + "TasksLibraryCategory": "പുസ്തകശാല", + "TasksApplicationCategory": "അപ്ലിക്കേഷൻ", + "TasksChannelsCategory": "ഇന്റർനെറ്റ് ചാനലുകൾ", + "TaskCleanActivityLog": "പ്രവർത്തന ലോഗ് വൃത്തിയാക്കുക", + "TaskCleanActivityLogDescription": "കോൺഫിഗർ ചെയ്‌ത പ്രായത്തേക്കാൾ പഴയ പ്രവർത്തന ലോഗ് എൻട്രികൾ ഇല്ലാതാക്കുന്നു.", + "TaskCleanCache": "കാഷെ ഡയറക്ടറി വൃത്തിയാക്കുക", + "TaskCleanCacheDescription": "സിസ്റ്റത്തിന് ഇനി ആവശ്യമില്ലാത്ത കാഷെ ഫയലുകൾ ഇല്ലാതാക്കുന്നു.", + "TaskRefreshChapterImages": "ചാപ്റ്റർ ഇമേജുകൾ എക്‌സ്‌ട്രാക്റ്റുചെയ്യുക", + "TaskRefreshChapterImagesDescription": "അധ്യായങ്ങളുള്ള വീഡിയോകൾക്കായി ലഘുചിത്രങ്ങൾ സൃഷ്ടിക്കുന്നു.", + "TaskRefreshLibrary": "മീഡിയ ലൈബ്രറി സ്കാൻ ചെയ്യുക", + "TaskRefreshLibraryDescription": "പുതിയ ഫയലുകൾക്കായി നിങ്ങളുടെ മീഡിയ ലൈബ്രറി സ്കാൻ ചെയ്യുകയും മെറ്റാഡാറ്റ പുതുക്കുകയും ചെയ്യുന്നു.", + "TaskCleanLogs": "ലോഗ് ഡയറക്ടറി വൃത്തിയാക്കുക", + "TaskCleanLogsDescription": "Log 0} ദിവസത്തിൽ കൂടുതൽ പഴക്കമുള്ള ലോഗ് ഫയലുകൾ ഇല്ലാതാക്കുന്നു.", + "TaskRefreshPeople": "ആളുകളെ പുതുക്കുക", + "TaskRefreshPeopleDescription": "നിങ്ങളുടെ മീഡിയ ലൈബ്രറിയിലെ അഭിനേതാക്കൾക്കും സംവിധായകർക്കും മെറ്റാഡാറ്റ അപ്‌ഡേറ്റുചെയ്യുന്നു.", + "TaskUpdatePlugins": "പ്ലഗിനുകൾ അപ്‌ഡേറ്റുചെയ്യുക", + "TaskUpdatePluginsDescription": "യാന്ത്രികമായി അപ്‌ഡേറ്റുചെയ്യുന്നതിന് കോൺഫിഗർ ചെയ്‌തിരിക്കുന്ന പ്ലഗിനുകൾക്കായുള്ള അപ്‌ഡേറ്റുകൾ ഡൗൺലോഡുചെയ്യുകയും ഇൻസ്റ്റാളുചെയ്യുകയും ചെയ്യുന്നു.", + "TaskCleanTranscode": "ട്രാൻസ്‌കോഡ് ഡയറക്‌ടറി വൃത്തിയാക്കുക", + "TaskCleanTranscodeDescription": "ഒരു ദിവസത്തിൽ കൂടുതൽ പഴക്കമുള്ള ട്രാൻസ്‌കോഡ് ഫയലുകൾ ഇല്ലാതാക്കുന്നു.", + "TaskRefreshChannels": "ചാനലുകൾ പുതുക്കുക", + "TaskRefreshChannelsDescription": "ഇന്റർനെറ്റ് ചാനൽ വിവരങ്ങൾ പുതുക്കുന്നു.", + "TaskDownloadMissingSubtitles": "നഷ്‌ടമായ സബ്‌ടൈറ്റിലുകൾ ഡൗൺലോഡുചെയ്യുക", + "TaskDownloadMissingSubtitlesDescription": "മെറ്റാഡാറ്റ കോൺഫിഗറേഷനെ അടിസ്ഥാനമാക്കി നഷ്‌ടമായ സബ്‌ടൈറ്റിലുകൾക്കായി ഇന്റർനെറ്റ് തിരയുന്നു.", + "ValueSpecialEpisodeName": "പ്രത്യേക - {0}", + "Collections": "ശേഖരങ്ങൾ", + "Folders": "ഫോൾഡറുകൾ", + "HeaderAlbumArtists": "കലാകാരന്റെ ആൽബം", + "Sync": "സമന്വയിപ്പിക്കുക", + "Movies": "സിനിമകൾ", + "Photos": "ഫോട്ടോകൾ", + "Albums": "ആൽബങ്ങൾ", + "Playlists": "പ്ലേലിസ്റ്റുകൾ", + "Songs": "ഗാനങ്ങൾ", + "HeaderContinueWatching": "കാണുന്നത് തുടരുക", + "Artists": "കലാകാരന്മാർ", + "Shows": "ഷോകൾ", + "Default": "സ്ഥിരസ്ഥിതി", + "Favorites": "പ്രിയപ്പെട്ടവ", + "Books": "പുസ്തകങ്ങൾ", + "Genres": "വിഭാഗങ്ങൾ", + "Channels": "ചാനലുകൾ", + "TaskOptimizeDatabaseDescription": "ഡാറ്റാബേസ് ചുരുക്കുകയും സ്വതന്ത്ര ഇടം വെട്ടിച്ചുരുക്കുകയും ചെയ്യുന്നു. ലൈബ്രറി സ്‌കാൻ ചെയ്‌തതിനുശേഷം അല്ലെങ്കിൽ ഡാറ്റാബേസ് പരിഷ്‌ക്കരണങ്ങളെ സൂചിപ്പിക്കുന്ന മറ്റ് മാറ്റങ്ങൾ ചെയ്‌തതിന് ശേഷം ഈ ടാസ്‌ക് പ്രവർത്തിപ്പിക്കുന്നത് പ്രകടനം മെച്ചപ്പെടുത്തും.", + "TaskOptimizeDatabase": "ഡാറ്റാബേസ് ഒപ്റ്റിമൈസ് ചെയ്യുക", + "HearingImpaired": "കേൾവി തകരാറുകൾ", + "External": "പുറമേയുള്ള", + "TaskKeyframeExtractorDescription": "കൂടുതൽ കൃത്യമായ HLS പ്ലേലിസ്റ്റുകൾ സൃഷ്‌ടിക്കുന്നതിന് വീഡിയോ ഫയലുകളിൽ നിന്ന് കീഫ്രെയിമുകൾ എക്‌സ്‌ട്രാക്‌റ്റ് ചെയ്യുന്നു. ഈ പ്രവർത്തനം പൂർത്തിയാവാൻ കുറച്ചധികം സമയം എടുത്തേക്കാം.", + "TaskKeyframeExtractor": "കീഫ്രെയിം എക്സ്ട്രാക്റ്റർ", + "TaskCleanCollectionsAndPlaylistsDescription": "നിലവിലില്ലാത്ത ശേഖരങ്ങളിൽ നിന്നും പ്ലേലിസ്റ്റുകളിൽ നിന്നും ഇനങ്ങൾ നീക്കംചെയ്യുന്നു.", + "TaskCleanCollectionsAndPlaylists": "ശേഖരങ്ങളും പ്ലേലിസ്റ്റുകളും വൃത്തിയാക്കുക", + "TaskAudioNormalization": "സാധാരണ ശബ്ദ നിലയിലെത്തിലെത്തിക്കുക", + "TaskAudioNormalizationDescription": "സാധാരണ ശബ്ദ നിലയിലെത്തിലെത്തിക്കുന്ന ഡാറ്റയ്ക്കായി ഫയലുകൾ സ്കാൻ ചെയ്യുക.", + "TaskRefreshTrickplayImages": "ട്രിക്ക് പ്ലേ ചിത്രങ്ങൾ സൃഷ്ടിക്കുക", + "TaskRefreshTrickplayImagesDescription": "പ്രവർത്തനക്ഷമമാക്കിയ ലൈബ്രറികളിൽ വീഡിയോകൾക്കായി ട്രിക്ക്പ്ലേ പ്രിവ്യൂകൾ സൃഷ്ടിക്കുന്നു." +} diff --git a/Emby.Server.Implementations/Localization/Core/mn.json b/Emby.Server.Implementations/Localization/Core/mn.json new file mode 100644 index 00000000..a684ff20 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/mn.json @@ -0,0 +1,141 @@ +{ + "Books": "Номнууд", + "HeaderNextUp": "Дараа нь", + "HeaderContinueWatching": "Үргэлжлүүлэн үзэх", + "Songs": "Дуунууд", + "Playlists": "Тоглуулах жагсаалтууд", + "Movies": "Кинонууд", + "Latest": "Сүүлийн үеийн", + "Genres": "Төрлүүд", + "Favorites": "Дуртай", + "Collections": "Цуглуулгууд", + "Artists": "Уран бүтээлчид", + "Albums": "Дуут цомгууд", + "TaskExtractMediaSegments": "Медиа сегмент шалга", + "TaskExtractMediaSegmentsDescription": "MediaSegment идэвхжүүлсэн залгаасуудаас медиа сегментүүдийг задлах эсвэл олж авах.", + "TaskMoveTrickplayImages": "Трикплэй зургуудын байршлыг шилжүүлэх", + "TaskMoveTrickplayImagesDescription": "Одоогоор байгаа трикплэй файлуудыг сангийн тохиргоонд тохируулан шилжүүлнэ.", + "TaskDownloadMissingLyrics": "Алга болсон дууны үгийг татаж авах", + "TaskDownloadMissingLyricsDescription": "Дууны үгийг татаж авах", + "TaskOptimizeDatabase": "Датабаазыг сайжруулах", + "TaskKeyframeExtractor": "Түлхүүр кадр гаргагч", + "TaskCleanCache": "Кэш санг цэвэрлэх", + "NewVersionIsAvailable": "Jellyfin Server-н шинэ хувилбар татаж авахад нээлттэй боллоо.", + "MessageNamedServerConfigurationUpdatedWithValue": "Server-н {0}-р хэсгийн тохиргоо шинэчлэгдлээ", + "NotificationOptionAudioPlaybackStopped": "Дууг зогсоов", + "NotificationOptionNewLibraryContent": "Шинэ агуулга орлоо", + "NotificationOptionServerRestartRequired": "Server-г дахин асаана уу", + "NotificationOptionVideoPlaybackStopped": "Бичлэгийг зогсоов", + "UserPasswordChangedWithName": "Хэрэглэгч {0}-н нууц үгийг өөрчиллөө", + "TaskCleanCollectionsAndPlaylists": "Цуглуулга ба тоглуулах жагсаалтыг цэвэрлэх", + "ScheduledTaskFailedWithName": "{0} амжилтгүй", + "StartupEmbyServerIsLoading": "Jellyfin Server ачааллаж байна. Хэсэг хугацааны дараа дахин оролдоно уу.", + "TaskCleanActivityLog": "Үйл ажиллагааны бүртгэлийг цэвэрлэх", + "SubtitleDownloadFailureFromForItem": "{0}-г {1}-д зориулсан хадмал орчуулгыг татаж авч чадсангүй", + "TaskRefreshLibraryDescription": "Таны медиа санг шинэ файлуудын хувьд шалгаж, мета мэдээллийг шинэчилнэ.", + "UserOfflineFromDevice": "{0}-г {1}-с салгалаа", + "ValueHasBeenAddedToLibrary": "{0}-г медиа сан руу нэмэгдлээ", + "TaskRefreshPeopleDescription": "Таны медиа санд байгаа жүжигчид болон найруулагчдын мета мэдээллийг шинэчилнэ.", + "TaskCleanTranscodeDescription": "Нэг өдрөөс илүү настай транскодлох файлуудыг устгана.", + "TaskRefreshChannelsDescription": "Интернет сувгуудын мэдээллийг шинэчлэх.", + "TaskDownloadMissingSubtitlesDescription": "Мета мэдээллийн тохиргоонд үндэслэн интернетээс алга болсон дэд гарчгийг хайна.", + "TaskOptimizeDatabaseDescription": "Мэдээллийн сантайг шахаж, чөлөөтэй зайг багасгана. Санг шалгаж, мэдээллийн сантай холбоотой өөрчлөлт хийхийн дараа энэ үйлдлийг гүйцэтгэх нь гүйцэтгэлийг сайжруулах боломжтой.", + "TaskKeyframeExtractorDescription": "Видео файлуудаас түлхүүр кадруудыг гаргаж, илүү нарийвчилсан HLS тоглуулах жагсаалт үүсгэнэ. Энэ үйлдэл удаан хугацаанд үргэлжлэх боломжтой.", + "NotificationOptionAudioPlayback": "Дууг тоглууллаа", + "TaskRefreshTrickplayImages": "Трикплэй зургуудыг үүсгэх", + "TaskUpdatePlugins": "Plugin-уудыг шинэчлэх", + "TaskCleanCollectionsAndPlaylistsDescription": "Одоо байхгүй болсон зүйлсийг цуглуулга ба тоглуулах жагсаалтаас устгана.", + "TaskAudioNormalization": "Аудиог хэвшүүлэх", + "TaskAudioNormalizationDescription": "Файлуудаас дууны хэвийн хэмжээсийн мэдээллийг шалгана.", + "TaskRefreshTrickplayImagesDescription": "Идэвхжсэн сангуудад байгаа видеонуудын трикплэй урьдчилсан харагдацыг үүсгэнэ.", + "TaskUpdatePluginsDescription": "Автомат шинэчлэлд тохируулсан залгаасуудын шинэчлэлтийг татаж авч суулгана.", + "TaskCleanTranscode": "Транскодлох санг цэвэрлэх", + "TaskRefreshChannels": "Сувгуудыг шинэчлэх", + "TaskDownloadMissingSubtitles": "Алга болсон хадмал орчуулгыг татах", + "External": "Гадны", + "HeaderFavoriteArtists": "Дуртай уран бүтээлчид", + "HeaderFavoriteEpisodes": "Дуртай ангиуд", + "HeaderFavoriteShows": "Дуртай нэвтрүүлэг", + "HeaderFavoriteSongs": "Дуртай дуу", + "AppDeviceValues": "Aпп: {0}, Төхөөрөмж: {1}", + "Application": "Aпп", + "AuthenticationSucceededWithUserName": "{0} амжилттай нэвтэрлээ", + "CameraImageUploadedFrom": "{0}-с шинэ зураг байршуулагдлаа", + "Channels": "Сувгууд", + "ChapterNameValue": "{0}-р бүлэг", + "Default": "Анхдагч", + "DeviceOfflineWithName": "{0}-н холболт саллаа", + "DeviceOnlineWithName": "{0} холбогдлоо", + "FailedLoginAttemptWithUserName": "{0}-н нэвтрэх оролдлого амжилтгүй", + "Folders": "Хавтасууд", + "Forced": "Хүчээр", + "HeaderAlbumArtists": "Цомгийн уран бүтээлчид", + "HeaderFavoriteAlbums": "Дуртай цомгууд", + "HeaderLiveTV": "Шууд ТВ", + "HeaderRecordingGroups": "Бичлэгийн бүлгүүд", + "HearingImpaired": "Сонсголын бэрхшээлтэй", + "HomeVideos": "Үндсэн дүрсүүд", + "Inherit": "Уламжлах", + "ItemAddedWithName": "{0}-г санд нэмлээ", + "ItemRemovedWithName": "{0}-с сангаас хаслаа", + "LabelIpAddressValue": "IP хаяг: {0}", + "LabelRunningTimeValue": "Үргэлжлэх хугацаа: {0}", + "MessageApplicationUpdated": "Jellyfin Server шинэчлэгдлээ", + "MessageApplicationUpdatedTo": "Jellyfin Server {0} болж шинэчлэгдлээ", + "MessageServerConfigurationUpdated": "Server-н тохиргоо шинэчлэгдлээ", + "MixedContent": "Холимог агуулга", + "Music": "Хөгжим", + "MusicVideos": "Дууны клипүүд", + "NameInstallFailed": "{0} суулгахад алдаа гарлаа", + "NameSeasonNumber": "{0}-р улирал", + "NameSeasonUnknown": "Улирал олдсонгүй", + "NotificationOptionApplicationUpdateAvailable": "Апп шинэчлэлт бий болсон байна", + "NotificationOptionApplicationUpdateInstalled": "Апп-н шинэчлэлийг суулгалаа", + "NotificationOptionCameraImageUploaded": "Камерын зураг орууллаа", + "NotificationOptionInstallationFailed": "Суулгалт амжилтгүй", + "NotificationOptionPluginError": "Plugin-д алдаа гарлаа", + "NotificationOptionPluginInstalled": "Plugin-г суулгалаа", + "NotificationOptionPluginUninstalled": "Plugin-г устгалаа", + "NotificationOptionPluginUpdateInstalled": "Plugin-ны шинэчлэн суулгалаа", + "NotificationOptionTaskFailed": "Товолсон ажил амжилтгүй", + "NotificationOptionUserLockedOut": "Хэрэглэгчийг түгжив", + "NotificationOptionVideoPlayback": "Бичлэгийг тоглуулж эхлэв", + "Photos": "Зургууд", + "Plugin": "Плагин", + "PluginInstalledWithName": "{0}-г суулгалаа", + "PluginUninstalledWithName": "{0}-г устгалаа", + "PluginUpdatedWithName": "{0}-г шинэчиллээ", + "ProviderValue": "Нийлүүлэгч: {0}", + "ScheduledTaskStartedWithName": "{0}-г эхлүүлэв", + "ServerNameNeedsToBeRestarted": "{0}-г дахин асаана уу", + "Shows": "Шоу", + "Sync": "Синхрончлох", + "System": "Систем", + "TvShows": "ТВ нэвтрүүлгүүд", + "Undefined": "Танисангүй", + "User": "Хэрэглэгч", + "UserCreatedWithName": "Хэрэглэгч {0}-г үүсгэлээ", + "UserDeletedWithName": "Хэрэглэгч {0}-г устгалаа", + "UserDownloadingItemWithValues": "{0} нь {1}-г татаж байна", + "UserLockedOutWithName": "Хэрэглэгч {0}-г түгжлээ", + "UserOnlineFromDevice": "{0} нь {1}-тэй холбоотой байна", + "UserPolicyUpdatedWithName": "Хэрэглэгчийн журмыг {0}-д зориулан шинэчиллээ", + "UserStartedPlayingItemWithValues": "{0}-г {2} дээр {1}-г тоглуулж байна", + "UserStoppedPlayingItemWithValues": "{0}-г {2} дээр {1}-г тоглуулж дуусгалаа", + "ValueSpecialEpisodeName": "Онцгой - {0}", + "VersionNumber": "Хувилбар {0}", + "TasksMaintenanceCategory": "Засвар", + "TasksLibraryCategory": "Сан", + "TasksApplicationCategory": "Апп", + "TasksChannelsCategory": "Интернет сувгууд", + "TaskCleanActivityLogDescription": "Тохируулсан хугацаанаас хуучин үйл ажиллагааны бүртгэлийн бичлэгүүдийг устгана.", + "TaskCleanLogs": "Бүртгэлийн санг цэвэрлэх", + "TaskCleanLogsDescription": "{0} өдрөөс илүү настай бүртгэлийн файлуудыг устгана.", + "TaskRefreshPeople": "Хүмүүсийг шинэчлэх", + "TaskCleanCacheDescription": "Системд хэрэггүй болсон кэш файлуудыг устгана.", + "TaskRefreshChapterImages": "Бүлгийн зураг авах", + "TaskRefreshChapterImagesDescription": "Бүлгүүдтэй видеонуудын хуудсан зураг үүсгэнэ.", + "TaskRefreshLibrary": "Медиа санг шалгах", + "CleanupUserDataTask": "Хэрэглэгчийн өгөгдлийн цэвэрлэгээний үүрэг", + "CleanupUserDataTaskDescription": "Хугацаа нь 90 хоногоос дээш хугацаанд байхгүй болсон медианаас бүх хэрэглэгчийн өгөгдлийг (үзсэн төлөв, дуртай жагсаалт гэх мэт) цэвэрлэнэ." +} diff --git a/Emby.Server.Implementations/Localization/Core/mr.json b/Emby.Server.Implementations/Localization/Core/mr.json new file mode 100644 index 00000000..727bbee1 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/mr.json @@ -0,0 +1,141 @@ +{ + "Books": "पुस्तकं", + "Artists": "संगीतकार", + "Albums": "अल्बम", + "Playlists": "प्लेलिस्ट", + "HeaderAlbumArtists": "अल्बम संगीतकार", + "Folders": "फोल्डर", + "HeaderFavoriteEpisodes": "आवडते भाग", + "HeaderFavoriteSongs": "आवडती गाणी", + "Movies": "चित्रपट", + "HeaderFavoriteArtists": "आवडते संगीतकार", + "Shows": "कार्यक्रम", + "HeaderFavoriteAlbums": "आवडते अल्बम", + "Channels": "वाहिन्या", + "ValueSpecialEpisodeName": "विशेष - {0}", + "HeaderFavoriteShows": "आवडते कार्यक्रम", + "Favorites": "आवडीचे", + "HeaderNextUp": "यानंतर", + "Songs": "गाणी", + "HeaderLiveTV": "लाइव्ह टीव्ही", + "Genres": "जाँनरे", + "Photos": "चित्र", + "TaskDownloadMissingSubtitles": "नसलेले सबटायटल डाउनलोड करा", + "TaskCleanTranscodeDescription": "एक दिवसापेक्षा जुन्या ट्रान्सकोड फायली काढून टाका.", + "TaskCleanTranscode": "ट्रान्सकोड डिरेक्टरी साफ करून टाका", + "TaskUpdatePlugins": "प्लगइन अपडेट करा", + "TaskCleanLogs": "लॉग डिरेक्टरी साफ करून टाका", + "TaskCleanCache": "कॅश डिरेक्टरी साफ करून टाका", + "TasksChannelsCategory": "इंटरनेट वाहिन्या", + "TasksApplicationCategory": "अ‍ॅप्लिकेशन", + "TasksLibraryCategory": "संग्रहालय", + "VersionNumber": "आवृत्ती {0}", + "UserPasswordChangedWithName": "{0} या प्रयोक्त्याचे पासवर्ड बदलण्यात आले आहे", + "UserOnlineFromDevice": "{0} हे {1} येथून ऑनलाइन आहेत", + "UserDeletedWithName": "प्रयोक्ता {0} काढून टाकण्यात आले आहे", + "UserCreatedWithName": "प्रयोक्ता {0} बनवण्यात आले आहे", + "User": "प्रयोक्ता", + "TvShows": "टीव्ही कार्यक्रम", + "StartupEmbyServerIsLoading": "जेलिफिन सर्व्हर लोड होत आहे. कृपया थोड्या वेळात पुन्हा प्रयत्न करा.", + "Plugin": "प्लगइन", + "NotificationOptionCameraImageUploaded": "कॅमेरा चित्र अपलोड केले आहे", + "NotificationOptionApplicationUpdateInstalled": "अ‍ॅप्लिकेशन अपडेट इन्स्टॉल केले आहे", + "NotificationOptionApplicationUpdateAvailable": "अ‍ॅप्लिकेशन अपडेट उपलब्ध आहे", + "NewVersionIsAvailable": "जेलिफिन सर्व्हरची एक नवीन आवृत्ती डाउनलोड करण्यास उपलब्ध आहे.", + "NameSeasonUnknown": "अज्ञात सीझन", + "NameSeasonNumber": "सीझन {0}", + "MusicVideos": "संगीत व्हिडीयो", + "Music": "संगीत", + "MessageApplicationUpdatedTo": "जेलिफिन सर्व्हर अपडेट होऊन {0} आवृत्तीवर पोहोचला आहे", + "MessageApplicationUpdated": "जेलिफिन सर्व्हर अपडेट केला गेला आहे", + "Latest": "नवीनतम", + "LabelIpAddressValue": "आयपी पत्ता: {0}", + "ItemRemovedWithName": "{0} हे संग्रहालयातून काढून टाकण्यात आले", + "ItemAddedWithName": "{0} हे संग्रहालयात जोडले गेले", + "HomeVideos": "घरचे व्हिडीयो", + "HeaderRecordingGroups": "रेकॉर्डिंग गट", + "CameraImageUploadedFrom": "एक नवीन कॅमेरा चित्र {0} येथून अपलोड केले आहे", + "Application": "अ‍ॅप्लिकेशन", + "AppDeviceValues": "अ‍ॅप: {0}, यंत्र: {1}", + "Collections": "संग्रह", + "ChapterNameValue": "धडा {0}", + "TaskDownloadMissingSubtitlesDescription": "नसलेल्या उपशिर्षकांचा मेटाडॅटा कॉन्फिग्युरेशनप्रमाणे इन्टरनेटवर शोध घेतो.", + "TaskRefreshChannelsDescription": "इन्टरनेट वाहिन्यांची माहिती ताजी करतो.", + "TaskUpdatePluginsDescription": "आपोआप अपडेट करण्यासाठी कॉन्फिगर केलेल्या प्लगइनसाठी अपडेट डाउनलोड करून इन्स्टॉल करतो.", + "TaskRefreshChannels": "वाहिन्या ताज्या करा", + "TaskRefreshPeopleDescription": "आपल्या माध्यम संग्रहातील अभिनेत्यांचा व दिग्दर्शकांचा मेटाडॅटा ताजा करतो.", + "TaskRefreshPeople": "लोकांची माहिती ताजी करा", + "TaskRefreshLibraryDescription": "माध्यम संग्रह स्कॅन करून नवीन फायली शोधतो व मेटाडॅटा ताजे करतो.", + "TaskRefreshLibrary": "माध्यम संग्रह स्कॅन करा", + "TaskRefreshChapterImagesDescription": "अध्याय असलेल्या व्हिडियोंसाठी थंबनेल चित्र बनवतो.", + "TaskRefreshChapterImages": "अध्याय चित्र काढून घ्या", + "TasksMaintenanceCategory": "देखरेख", + "ValueHasBeenAddedToLibrary": "{0} हे तुमच्या माध्यम संग्रहात जोडण्यात आले आहे", + "UserStoppedPlayingItemWithValues": "{0} यांचं {2} वर {1} पूर्णपणे प्ले करून झालं आहे", + "UserStartedPlayingItemWithValues": "{0} हे {2} वर {1} प्ले करत आहे", + "UserDownloadingItemWithValues": "{0} हे {1} डाउनलोड करत आहे", + "System": "प्रणाली", + "Undefined": "अव्याख्यात", + "Sync": "सिंक", + "ServerNameNeedsToBeRestarted": "{0} याला बंद करून पुन्हा सुरू करायची गरज आहे", + "SubtitleDownloadFailureFromForItem": "{0} येथून {1} यासाठी उपशिर्षक डाउनलोड करण्यात अपयश", + "ScheduledTaskStartedWithName": "{0} सुरू झाले", + "ScheduledTaskFailedWithName": "{0} अपयशी झाले", + "ProviderValue": "पुरवणारा: {0}", + "PluginUpdatedWithName": "{0} अपडेट केले", + "PluginUninstalledWithName": "{0} अनिन्स्टॉल केले", + "PluginInstalledWithName": "{0} इन्स्टॉल केले", + "NotificationOptionVideoPlaybackStopped": "व्हिडियो प्लेबॅक बंद केले", + "NotificationOptionVideoPlayback": "व्हिडियो प्लेबॅक सुरू केले", + "NotificationOptionTaskFailed": "अनुसूचित कार्यात अपयश", + "NotificationOptionServerRestartRequired": "सर्व्हर बंद करून पुन्हा सुरू करावा लागेल", + "NotificationOptionPluginUpdateInstalled": "प्लगइन अपडेट इन्स्टॉल झाले", + "NotificationOptionPluginUninstalled": "प्लगइन अनिन्स्टॉल झाले", + "NotificationOptionPluginInstalled": "प्लगइन इन्स्टॉल झाले", + "NotificationOptionPluginError": "प्लगइनमध्ये अपयश", + "NotificationOptionNewLibraryContent": "नवीन सामग्री जोडली गेली", + "NotificationOptionInstallationFailed": "इन्स्टॉल करण्यात अपयश", + "NotificationOptionAudioPlayback": "ऑडियो प्लेबॅक सुरू झाले", + "NotificationOptionAudioPlaybackStopped": "ऑडियो प्लेबॅक बंद झाले", + "MixedContent": "मिश्रित सामग्री", + "LabelRunningTimeValue": "चालू काल: {0}", + "HeaderContinueWatching": "बघणे चालू ठेवा", + "Default": "डीफॉल्ट", + "TaskKeyframeExtractorDescription": "अधिक अचूक HLS प्लेलिस्ट तयार करण्यासाठी व्हिडिओ फाइल्समधून कीफ्रेम काढते. हे कार्य दीर्घकाळ चालू शकते.", + "TaskKeyframeExtractor": "कीफ्रेम एक्स्ट्रॅक्टर", + "TaskOptimizeDatabaseDescription": "डेटाबेस कॉम्पॅक्ट करतो आणि मोकळी जागा कमी करतो. लायब्ररी स्कॅन केल्यावर किंवा डेटाबेस बदल सुचवणारे इतर बदल केल्यावर हे कार्य चालवल्याने कार्यप्रदर्शन सुधारू शकते.", + "TaskOptimizeDatabase": "डेटाबेस ऑप्टिमाइझ करा", + "TaskCleanLogsDescription": "{0} दिवसांपेक्षा जुन्या लॉग फाइल्स हटवा.", + "TaskCleanCacheDescription": "सिस्टमला यापुढे आवश्यक नसलेल्या कॅशे फाइल्स हटवा.", + "TaskCleanActivityLogDescription": "कॉन्फिगर केलेल्या वयापेक्षा जुन्या क्रियाकलाप लॉग एंट्री हटवा.", + "TaskCleanActivityLog": "क्रियाकलाप लॉग साफ करा", + "UserPolicyUpdatedWithName": "{0} साठी वापरकर्ता धोरण अपडेट केले गेले आहे", + "UserOfflineFromDevice": "{0} {1} वरून डिस्कनेक्ट झाला आहे", + "UserLockedOutWithName": "वापरकर्ता {0} लॉक केले गेले आहे", + "NotificationOptionUserLockedOut": "वापरकर्ता लॉक आउट", + "NameInstallFailed": "{0} स्थापना अयशस्वी", + "MessageServerConfigurationUpdated": "सर्व्हर कॉन्फिगरेशन अद्यतनित केले आहे", + "MessageNamedServerConfigurationUpdatedWithValue": "सर्व्हर कॉन्फिगरेशन विभाग {0} अद्यतनित केला गेला आहे", + "Inherit": "वारसा", + "Forced": "सक्ती केली आहे", + "FailedLoginAttemptWithUserName": "{0} कडून लॉगिन करण्याचा प्रयत्न अयशस्वी झाला", + "External": "बाहेरचा", + "DeviceOnlineWithName": "{0} कनेक्ट झाले", + "DeviceOfflineWithName": "{0} डिस्कनेक्ट झाला आहे", + "AuthenticationSucceededWithUserName": "{0} यशस्वीरित्या प्रमाणीकृत", + "HearingImpaired": "कर्णबधीर", + "TaskRefreshTrickplayImages": "ट्रिकप्ले प्रतिमा तयार करा", + "TaskRefreshTrickplayImagesDescription": "सक्षम लायब्ररीमधील व्हिडिओंसाठी ट्रिकप्ले पूर्वावलोकन तयार करते.", + "TaskCleanCollectionsAndPlaylists": "संग्रह आणि प्लेलिस्ट व्यवस्थित करा", + "TaskExtractMediaSegments": "मिडिया विभाग तपासणी", + "TaskMoveTrickplayImages": "ट्रिकप्ले प्रतिमेचे स्थान स्थलांतर करा", + "TaskDownloadMissingLyrics": "उपलब्ध नसलेली गीतपट्टी (Lyrics) डाउनलोड करा", + "TaskAudioNormalization": "ऑडिओ सामान्यीकरण", + "TaskAudioNormalizationDescription": "ऑडिओ सामान्यीकरणाचा डाटा स्कॅन करतो.", + "TaskDownloadMissingLyricsDescription": "गाण्यांची गीतपट्टी (Lyrics) डाउनलोड करतो", + "TaskExtractMediaSegmentsDescription": "सक्रिय असलेल्या प्लगिनमधून मीडिया विभाग प्राप्त करते.", + "TaskMoveTrickplayImagesDescription": "लायब्ररीच्या सेटिंग्जप्रमाणे आधीपासून अस्तित्वात असलेल्या ट्रिकप्ले फाइल्सचे स्थान बदलते.", + "TaskCleanCollectionsAndPlaylistsDescription": "जे संग्रह आणि प्लेलिस्ट आता अस्तित्वात नाहीत, त्यांमधील घटक हटवते.", + "CleanupUserDataTask": "वापरकर्ता डेटाची स्वच्छता प्रक्रिया", + "CleanupUserDataTaskDescription": "९० दिवसांहून अधिक काळ अनुपस्थित असलेल्या माध्यमांवरील सर्व वापरकर्ता माहिती (जसे पाहण्याची स्थिती, आवडी इ.) हटवते." +} diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json new file mode 100644 index 00000000..2be04be8 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -0,0 +1,141 @@ +{ + "Albums": "Album", + "AppDeviceValues": "Aplikasi: {0}, Peranti: {1}", + "Application": "Aplikasi", + "Artists": "Artis", + "AuthenticationSucceededWithUserName": "{0} berjaya disahkan", + "Books": "Buku", + "CameraImageUploadedFrom": "Gambar baharu telah dimuat naik melalui {0}", + "Channels": "Saluran", + "ChapterNameValue": "Bab {0}", + "Collections": "Koleksi", + "DeviceOfflineWithName": "{0} telah dinyahsambung", + "DeviceOnlineWithName": "{0} telah disambung", + "FailedLoginAttemptWithUserName": "Percubaan gagal log masuk daripada {0}", + "Favorites": "Kegemaran", + "Folders": "Folder-folder", + "Genres": "Genre-genre", + "HeaderAlbumArtists": "Album artis-artis", + "HeaderContinueWatching": "Teruskan Menonton", + "HeaderFavoriteAlbums": "Album-album Kegemaran", + "HeaderFavoriteArtists": "Artis-artis Kegemaran", + "HeaderFavoriteEpisodes": "Episod-episod Kegemaran", + "HeaderFavoriteShows": "Rancangan-rancangan Kegemaran", + "HeaderFavoriteSongs": "Lagu-lagu Kegemaran", + "HeaderLiveTV": "TV Siaran Langsung", + "HeaderNextUp": "Seterusnya", + "HeaderRecordingGroups": "Kumpulan-kumpulan Rakaman", + "HomeVideos": "Video Peribadi", + "Inherit": "Warisi", + "ItemAddedWithName": "{0} telah ditambah ke dalam pustaka", + "ItemRemovedWithName": "{0} telah dibuang daripada pustaka", + "LabelIpAddressValue": "Alamat IP: {0}", + "LabelRunningTimeValue": "Masa berjalan: {0}", + "Latest": "Terbaharu", + "MessageApplicationUpdated": "Pelayan Jellyfin telah dikemas kini", + "MessageApplicationUpdatedTo": "Pelayan Jellyfin telah dikemas kini ke {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Konfigurasi pelayan bahagian {0} telah dikemas kini", + "MessageServerConfigurationUpdated": "Konfigurasi pelayan telah dikemas kini", + "MixedContent": "Kandungan campuran", + "Movies": "Filem-filem", + "Music": "Muzik", + "MusicVideos": "Video Muzik", + "NameInstallFailed": "{0} pemasangan gagal", + "NameSeasonNumber": "Musim ke-{0}", + "NameSeasonUnknown": "Musim Tidak Diketahui", + "NewVersionIsAvailable": "Versi terbaharu Pelayan Jellyfin telah tersedia untuk dimuat turun.", + "NotificationOptionApplicationUpdateAvailable": "Kemas kini aplikasi telah tersedia", + "NotificationOptionApplicationUpdateInstalled": "Kemas kini aplikasi telah dipasang", + "NotificationOptionAudioPlayback": "Ulangmain audio bermula", + "NotificationOptionAudioPlaybackStopped": "Ulangmain audio dihentikan", + "NotificationOptionCameraImageUploaded": "Imej kamera telah dimuatnaik", + "NotificationOptionInstallationFailed": "Pemasangan gagal", + "NotificationOptionNewLibraryContent": "Kandungan baru telah ditambah", + "NotificationOptionPluginError": "Kegagalan plugin", + "NotificationOptionPluginInstalled": "Plugin telah dipasang", + "NotificationOptionPluginUninstalled": "Plugin telah dinyahpasang", + "NotificationOptionPluginUpdateInstalled": "Kemaskini plugin telah dipasang", + "NotificationOptionServerRestartRequired": "Perlu mulakan semula server", + "NotificationOptionTaskFailed": "Kegagalan tugas berjadual", + "NotificationOptionUserLockedOut": "Pengguna telah dikunci", + "NotificationOptionVideoPlayback": "Ulangmain video bermula", + "NotificationOptionVideoPlaybackStopped": "Ulangmain video dihentikan", + "Photos": "Gambar-gambar", + "Playlists": "Senarai ulangmain", + "Plugin": "Plugin", + "PluginInstalledWithName": "{0} telah dipasang", + "PluginUninstalledWithName": "{0} telah dinyahpasang", + "PluginUpdatedWithName": "{0} telah dikemaskini", + "ProviderValue": "Pembekal: {0}", + "ScheduledTaskFailedWithName": "{0} gagal", + "ScheduledTaskStartedWithName": "{0} bermula", + "ServerNameNeedsToBeRestarted": "{0} perlu di ulangmula", + "Shows": "Tayangan", + "Songs": "Lagu-lagu", + "StartupEmbyServerIsLoading": "Pelayan Jellyfin sedang dimuatkan. Sila cuba sebentar lagi.", + "SubtitleDownloadFailureFromForItem": "Muat turun sarikata gagal dari {0} untuk {1}", + "Sync": "Segerak", + "System": "Sistem", + "TvShows": "Tayangan TV", + "User": "Pengguna", + "UserCreatedWithName": "Pengguna {0} telah diwujudkan", + "UserDeletedWithName": "Pengguna {0} telah dipadamkan", + "UserDownloadingItemWithValues": "{0} sedang memuat turun {1}", + "UserLockedOutWithName": "Pengguna {0} telah dikunci", + "UserOfflineFromDevice": "{0} telah terputus dari {1}", + "UserOnlineFromDevice": "{0} berada dalam talian dari {1}", + "UserPasswordChangedWithName": "Kata laluan telah ditukar bagi pengguna {0}", + "UserPolicyUpdatedWithName": "Dasar pengguna telah dikemas kini untuk {0}", + "UserStartedPlayingItemWithValues": "{0} sedang dimainkan {1} pada {2}", + "UserStoppedPlayingItemWithValues": "{0} telah tamat dimainkan {1} pada {2}", + "ValueHasBeenAddedToLibrary": "{0} telah ditambah ke media library anda", + "ValueSpecialEpisodeName": "Khas - {0}", + "VersionNumber": "Versi {0}", + "TaskCleanActivityLog": "Log Aktiviti Bersih", + "TasksChannelsCategory": "Saluran Internet", + "TasksApplicationCategory": "Aplikasi", + "TasksLibraryCategory": "Perpustakaan", + "TasksMaintenanceCategory": "Penyelenggaraan", + "Undefined": "Tidak ditentukan", + "Forced": "Dipaksa", + "Default": "Default", + "TaskCleanCache": "Bersihkan Direktori Cache", + "TaskCleanActivityLogDescription": "Padamkan entri log aktiviti yang lebih tua daripada usia yang dikonfigurasi.", + "TaskRefreshPeople": "Segarkan Orang", + "TaskCleanLogsDescription": "Padamkan fail log yang berumur lebih dari {0} hari.", + "TaskCleanLogs": "Bersihkan Direktotri Log", + "TaskRefreshLibraryDescription": "Imbas perpustakaan media untuk mencari fail-fail baru dan menyegarkan metadata.", + "TaskRefreshLibrary": "Imbas Perpustakaan Media", + "TaskRefreshChapterImagesDescription": "Membuat gambaran kecil untuk video yang mempunyai bab.", + "TaskRefreshChapterImages": "Ekstrak Gambar-gambar Bab", + "TaskCleanCacheDescription": "Menghapuskan fail cache yang tidak lagi diperlukan oleh sistem.", + "HearingImpaired": "Lemah Pendengaran", + "TaskRefreshPeopleDescription": "Kemas kini metadata untuk pelakon dan pengarah di dalam perpustakaan media.", + "TaskUpdatePluginsDescription": "Muat turun dan kemas kini plugin yang dikonfigurasi secara automatik.", + "TaskDownloadMissingSubtitlesDescription": "Cari sari kata yang hilang di internet, berdasarkan konfigurasi metadata.", + "TaskOptimizeDatabaseDescription": "Mampatkan pangkalan data dan potong ruang kosong. Pelaksanaan tugas ini selepas pengimbasan perpustakaan boleh membantu membaiki prestasi.", + "TaskRefreshChannels": "Segarkan Saluran-saluran", + "TaskUpdatePlugins": "Kemas kini plugin", + "TaskDownloadMissingSubtitles": "Muat turn sari kata yang tiada", + "TaskCleanTranscodeDescription": "Padam fail transkod yang lebih lama dari satu hari.", + "TaskRefreshChannelsDescription": "Segarkan maklumat saluran internet.", + "TaskCleanTranscode": "Bersihkan direktori transkod", + "External": "Luaran", + "TaskOptimizeDatabase": "Optimumkan pangkalan data", + "TaskKeyframeExtractor": "Ekstrak bingkai kunci", + "TaskKeyframeExtractorDescription": "Ekstrak bingkai kunci dari fail video untuk membina HLS playlist yang lebih tepat. Tugas ini mungkin perlukan masa yang panjang.", + "TaskRefreshTrickplayImagesDescription": "Jana gambar prebiu Trickplay untuk video dalam perpustakaan.", + "TaskRefreshTrickplayImages": "Jana gambar Trickplay", + "TaskExtractMediaSegments": "Imbasan Segmen Media", + "TaskExtractMediaSegmentsDescription": "Mengekstrak atau mendapatkan segmen media daripada pemalam yang didayakan MediaSegment.", + "TaskMoveTrickplayImagesDescription": "Mengalihkan fail trickplay sedia ada mengikut tetapan pustakan digital.", + "TaskDownloadMissingLyrics": "Muat turun lirik yang hilang", + "TaskDownloadMissingLyricsDescription": "Memuat turun lirik-lirik untuk lagu-lagu", + "TaskMoveTrickplayImages": "Alih Lokasi Imej Trickplay", + "TaskCleanCollectionsAndPlaylists": "Bersihkan koleksi dan senarai audio video", + "TaskAudioNormalization": "Normalisasi Audio", + "TaskAudioNormalizationDescription": "Mengimbas fail-fail untuk data normalisasi audio.", + "TaskCleanCollectionsAndPlaylistsDescription": "Mengalih keluar item daripada koleksi dan senarai audio video yang tidak wujud lagi.", + "CleanupUserDataTaskDescription": "Membersihkan semua data pengguna (keadaan tontonan, status kegemaran, dan sebagainya) daripada media yang tidak lagi wujud sekurang-kurangnya selama 90 hari.", + "CleanupUserDataTask": "Tugas pembersihan data pengguna" +} diff --git a/Emby.Server.Implementations/Localization/Core/mt.json b/Emby.Server.Implementations/Localization/Core/mt.json new file mode 100644 index 00000000..f7501ab4 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/mt.json @@ -0,0 +1,139 @@ +{ + "Albums": "Albums", + "AppDeviceValues": "Applikazzjoni: {0}, Device: {1}", + "Application": "Applikazzjoni", + "Artists": "Artisti", + "AuthenticationSucceededWithUserName": "{1} awtentikat b'suċċess", + "Books": "Kotba", + "CameraImageUploadedFrom": "Ttella' ritratt ġdid tal-kamera minn {1}", + "Channels": "Stazzjonijiet", + "ChapterNameValue": "Kapitlu {0}", + "Collections": "Kollezzjonijiet", + "DeviceOfflineWithName": "{0} tneħħa", + "DeviceOnlineWithName": "{0} tqabbad", + "External": "Estern", + "FailedLoginAttemptWithUserName": "Attentat fallut ta' login minn {0}", + "Favorites": "Favoriti", + "Forced": "Sfurzat", + "Genres": "Ġeneri", + "HeaderAlbumArtists": "Artisti tal-album", + "HeaderContinueWatching": "Kompli Ara", + "HeaderFavoriteAlbums": "Albums Favoriti", + "HeaderFavoriteArtists": "Artisti Favoriti", + "HeaderFavoriteEpisodes": "Episodji Favoriti", + "HeaderFavoriteShows": "Programmi Favoriti", + "HeaderFavoriteSongs": "Kanzunetti Favoriti", + "HeaderNextUp": "Li Jmiss", + "SubtitleDownloadFailureFromForItem": "Is-sottotitli ma setgħux jitniżżlu minn {0} għal {1}", + "UserPasswordChangedWithName": "Il-password għall-utent {0} inbidlet", + "TaskUpdatePluginsDescription": "Iniżżel u jinstalla aġġornamenti għal plugins li huma kkonfigurati biex jaġġornaw awtomatikament.", + "TaskDownloadMissingSubtitlesDescription": "Ifittex fuq l-internet għal sottotitli neqsin skont il-konfigurazzjoni tal-metadata.", + "TaskOptimizeDatabaseDescription": "Jikkompatta d-database u jaqta' l-ispazju ħieles. It-tħaddim ta' dan it-task wara li tiskennja l-librerija jew tagħmel bidliet oħra li jimplikaw modifiki fid-database jistgħu jtejbu l-mod kif jaħdem.", + "Default": "Standard", + "Folders": "Folders", + "HeaderLiveTV": "TV Dirett", + "HeaderRecordingGroups": "Gruppi ta' Rikordjar", + "HearingImpaired": "Nuqqas ta' Smigħ", + "HomeVideos": "Filmati Personali", + "Inherit": "Jiret", + "ItemAddedWithName": "{0} żdied fil-librerija", + "ItemRemovedWithName": "{0} tneħħa mil-librerija", + "LabelIpAddressValue": "Indirizz tal-IP: {0}", + "Latest": "Tal-Aħħar", + "MessageApplicationUpdated": "Il-Jellyfin Server ġie aġġornat", + "MessageApplicationUpdatedTo": "Il-JellyFin Server ġie aġġornat għal {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Is-sezzjoni {0} tal-konfigurazzjoni tas-server ġiet aġġornata", + "MessageServerConfigurationUpdated": "Il-konfigurazzjoni tas-server ġiet aġġornata", + "MixedContent": "Kontenut imħallat", + "Movies": "Films", + "Music": "Mużika", + "MusicVideos": "Music Videos", + "NameInstallFailed": "L-installazzjoni ta' {0} falliet", + "NameSeasonNumber": "Staġun {0}", + "NameSeasonUnknown": "Staġun Mhux Magħruf", + "NewVersionIsAvailable": "Verżjoni ġdida tal-Jellyfin Server hija disponibbli biex titniżżel.", + "NotificationOptionApplicationUpdateAvailable": "Hemm aġġornament tal-applikazzjoni", + "NotificationOptionCameraImageUploaded": "Ritratt tal-kamera mtella'", + "LabelRunningTimeValue": "Tul: {0}", + "NotificationOptionApplicationUpdateInstalled": "Aġġornament tal-applikazzjoni ġie installat", + "NotificationOptionAudioPlayback": "Beda l-playback tal-awdjo", + "NotificationOptionAudioPlaybackStopped": "Il-playback tal-awdjo twaqqaf", + "NotificationOptionInstallationFailed": "L-Installazzjoni falliet", + "NotificationOptionNewLibraryContent": "Kontenut ġdid żdied", + "NotificationOptionPluginError": "Falliment fil-plugin", + "NotificationOptionPluginInstalled": "Plugin installat", + "NotificationOptionPluginUninstalled": "Plugin tneħħa", + "NotificationOptionServerRestartRequired": "Hemm bżonn li tagħmel restart tas-server", + "NotificationOptionTaskFailed": "Falliment tat-task skedat", + "NotificationOptionUserLockedOut": "Utent imsakkar", + "Photos": "Ritratti", + "Playlists": "Playlists", + "Plugin": "Plugin", + "PluginInstalledWithName": "{0} ġie installat", + "PluginUninstalledWithName": "{0} tneħħa", + "PluginUpdatedWithName": "{0} ġie aġġornat", + "ProviderValue": "Fornitur: {0}", + "ScheduledTaskFailedWithName": "{0} falla", + "ScheduledTaskStartedWithName": "{0} beda", + "ServerNameNeedsToBeRestarted": "{0} jeħtieġ restart", + "Songs": "Kanzunetti", + "StartupEmbyServerIsLoading": "Jellyfin Server qed jillowdja. Jekk jogħġbok erġa' pprova ftit tal-ħin oħra.", + "Sync": "Sinkronizza", + "System": "Sistema", + "Undefined": "Bla Definizzjoni", + "User": "Utent", + "UserCreatedWithName": "L-utent {0} inħoloq", + "UserDeletedWithName": "L-utent {0} tħassar", + "UserDownloadingItemWithValues": "{0} qed iniżżel {1}", + "UserLockedOutWithName": "L-utent {0} ġie msakkar", + "UserOfflineFromDevice": "{0} skonnettja minn {1}", + "UserOnlineFromDevice": "{0} huwa online minn {1}", + "NotificationOptionPluginUpdateInstalled": "Aġġornament ta' plugin ġie installat", + "NotificationOptionVideoPlayback": "Il-playback tal-filmat beda", + "NotificationOptionVideoPlaybackStopped": "Il-playback tal-filmat twaqqaf", + "Shows": "Serje", + "TvShows": "Serje Televiżivi", + "UserPolicyUpdatedWithName": "Il-politka tal-utent ġiet aġġornata għal {0}", + "UserStartedPlayingItemWithValues": "{0} qed jara {1} fuq {2}", + "UserStoppedPlayingItemWithValues": "{0} waqaf jara {1} fuq {2}", + "ValueHasBeenAddedToLibrary": "{0} ġie miżjud mal-librerija tal-midja tiegħek", + "ValueSpecialEpisodeName": "Speċjali - {0}", + "VersionNumber": "Verżjoni {0}", + "TasksMaintenanceCategory": "Manutenzjoni", + "TasksLibraryCategory": "Librerija", + "TasksApplicationCategory": "Applikazzjoni", + "TasksChannelsCategory": "Stazzjonijiet tal-Internet", + "TaskCleanActivityLog": "Naddaf il-Logg tal-Attività", + "TaskCleanActivityLogDescription": "Iħassar id-daħliet tar-reġistru tal-attività eqdem mill-età li kienet kkonfigurata.", + "TaskCleanCache": "Naddaf id-Direttorju tal-Cache", + "TaskCleanCacheDescription": "Iħassar il-fajls tal-cache li m'għadhomx meħtieġa mis-sistema.", + "TaskRefreshChapterImages": "Oħroġ ir-Ritratti tal-Kapitlu", + "TaskRefreshChapterImagesDescription": "Joħloq thumbnails għal vidjows li għandhom kapitli.", + "TaskAudioNormalization": "Normalizzazzjoni tal-Awdjo", + "TaskAudioNormalizationDescription": "Skennja fajls għal data fuq in-normalizzazzjoni tal-awdjo.", + "TaskRefreshLibrary": "Skennja l-Librerija tal-Midja", + "TaskRefreshLibraryDescription": "Jiskennja l-librerija tal-midja tiegħek għal fajls ġodda u jġedded il-metadejta.", + "TaskCleanLogs": "Naddaf id-Direttorju tal-Logg", + "TaskCleanLogsDescription": "Iħassar fajls tal-logg eqdem minn {0} ijiem.", + "TaskRefreshPeople": "Aġġorna l-Persuni", + "TaskRefreshPeopleDescription": "Jaġġorna l-metadata għall-atturi u d-diretturi fil-librerija tal-midja tiegħek.", + "TaskRefreshTrickplayImages": "Iġġenera Stampi Trickplay", + "TaskRefreshTrickplayImagesDescription": "Joħloq previews trickplay għal videos fil-libreriji li għalihom hi attivata.", + "TaskUpdatePlugins": "Aġġorna l-Plugins", + "TaskCleanTranscode": "Naddaf id-Direttorju tat-Transcoding", + "TaskCleanTranscodeDescription": "Iħassar fajls tat-transcoding li huma eqdem minn ġurnata.", + "TaskRefreshChannels": "Aġġorna l-Istazzjonijiet", + "TaskRefreshChannelsDescription": "Aġġorna l-informazzjoni tal-istazzjonijiet tal-internet.", + "TaskDownloadMissingSubtitles": "Niżżel is-sottotitli nieqsa", + "TaskOptimizeDatabase": "Ottimiżża d-database", + "TaskKeyframeExtractor": "Estrattur ta' Keyframes", + "TaskKeyframeExtractorDescription": "Jiġbed il-keyframes mill-fajls tal-videos biex jagħmel playlists HLS aktar preċiżi. Dan it-task jista' jdum żmien twil biex ilesti.", + "TaskCleanCollectionsAndPlaylists": "Naddaf il-kollezzjonijiet u l-playlists", + "TaskCleanCollectionsAndPlaylistsDescription": "Ineħħi oġġetti minn kollezzjonijiet u playlists li m'għadhomx jeżistu.", + "TaskDownloadMissingLyrics": "Niżżel il-lirika nieqsa", + "TaskDownloadMissingLyricsDescription": "Iniżżel il-lirika għal-kanzunetti", + "TaskExtractMediaSegments": "Scan tas-Sezzjoni tal-Midja", + "TaskExtractMediaSegmentsDescription": "Jestratta jew iġib sezzjonijiet tal-midja minn plugins attivati tal-MediaSegment.", + "TaskMoveTrickplayImages": "Mexxi l-post tat-Trickplay Image", + "TaskMoveTrickplayImagesDescription": "Tmexxi l-files tat-trickplay li jeżistu skont kif inhi kkonfigurata l-librerija." +} diff --git a/Emby.Server.Implementations/Localization/Core/my.json b/Emby.Server.Implementations/Localization/Core/my.json new file mode 100644 index 00000000..097d0d2f --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/my.json @@ -0,0 +1,132 @@ +{ + "Default": "ပုံသေ", + "Collections": "စုစည်းမှုများ", + "Channels": "တီဗွီလိုင်းများ", + "Books": "စာအုပ်များ", + "Artists": "အနုပညာရှင်များ", + "Albums": "သီချင်းအခွေများ", + "TaskOptimizeDatabaseDescription": "ဒေတာဘေ့စ်ကို ကျစ်လစ်စေပြီး နေရာလွတ်များကို ဖြတ်တောက်ပေးသည်။ စာကြည့်တိုက်ကို စကင်န်ဖတ်ပြီးနောက် ဤလုပ်ငန်းကို လုပ်ဆောင်ခြင်း သို့မဟုတ် ဒေတာဘေ့စ်မွမ်းမံမှုများ စွမ်းဆောင်ရည်ကို မြှင့်တင်ပေးနိုင်သည်ဟု ရည်ညွှန်းသော အခြားပြောင်းလဲမှုများကို လုပ်ဆောင်ခြင်း။.", + "TaskOptimizeDatabase": "ဒေတာဘေ့စ်ကို အကောင်းဆုံးဖြစ်အောင်လုပ်ပါ", + "TaskDownloadMissingSubtitlesDescription": "မက်တာဒေတာ ဖွဲ့စည်းမှုပုံစံအပေါ် အခြေခံ၍ ပျောက်ဆုံးနေသော စာတန်းထိုးများအတွက် အင်တာနက်ကို ရှာဖွေသည်။", + "TaskDownloadMissingSubtitles": "ပျောက်ဆုံးနေသော စာတန်းထိုးများကို ဒေါင်းလုဒ်လုပ်ပါ", + "TaskRefreshChannelsDescription": "အင်တာနက်ချန်နယ်အချက်အလက်ကို ပြန်လည်စတင်သည်။", + "TaskRefreshChannels": "ချန်နယ်များကို ပြန်လည်စတင်ပါ", + "TaskCleanTranscodeDescription": "သက်တမ်း တစ်ရက်ထက်ပိုသော အသွင်ပြောင်းကုဒ်ဖိုင်များကို ဖျက်ပါ။", + "TaskCleanTranscode": "Transcode လမ်းညွှန်ကို သန့်ရှင်းပါ", + "TaskUpdatePluginsDescription": "အလိုအလျောက် အပ်ဒိတ်လုပ်ရန် စီစဉ်ထားသော ပလပ်အင်များအတွက် အပ်ဒိတ်များကို ဒေါင်းလုဒ်လုပ်ပြီး ထည့်သွင်းပါ။", + "TaskUpdatePlugins": "ပလပ်အင်များကို အပ်ဒိတ်လုပ်ပါ", + "TaskRefreshPeopleDescription": "သင့်မီဒီယာစာကြည့်တိုက်ရှိ သရုပ်ဆောင်များနှင့် ဒါရိုက်တာများအတွက် မက်တာဒေတာကို အပ်ဒိတ်လုပ်ပါ။", + "TaskRefreshPeople": "လူများကို ပြန်လည်ဆန်းသစ်ပါ", + "TaskCleanLogsDescription": "{0} ရက်ထက်ပိုသော မှတ်တမ်းဖိုင်များကို ဖျက်သည်။", + "TaskCleanLogs": "မှတ်တမ်းလမ်းညွှန်ကို သန့်ရှင်းပါ", + "TaskRefreshLibraryDescription": "သင့်မီဒီယာဒစ်ဂျစ်တိုက်ကို ဖိုင်အသစ်များရှိမရှိ စကင်န်ဖတ်ပြီး ဖိုင်ရဲ့အကြောင်းအရာများ ကို ပြန်ပြုပြင်မွမ်းမံပါ။", + "TaskRefreshLibrary": "မီဒီယာစာကြည့်တိုက်ကို စကင်န်ဖတ်ပါ", + "TaskRefreshChapterImagesDescription": "အခန်းများပါရှိသော ဗီဒီယိုများအတွက် ပုံသေးများကို ဖန်တီးပါ။", + "TaskRefreshChapterImages": "အခန်းတစ်ခုစီ ပုံများကို ထုတ်ယူပါ", + "TaskCleanCacheDescription": "စနစ်မှ မလိုအပ်တော့သော ကက်ရှ်ဖိုင်များကို ဖျက်ပါ။.", + "TaskCleanCache": "Cache Directory ကို ရှင်းပါ", + "TaskCleanActivityLogDescription": "စီစဉ်သတ်မှတ်ထားသော အသက်ထက် ပိုကြီးသော လုပ်ဆောင်ချက်မှတ်တမ်းများကို ဖျက်ပါ။", + "TaskCleanActivityLog": "လုပ်ဆောင်ချက်မှတ်တမ်းကို ရှင်းလင်းပါ", + "TasksChannelsCategory": "အင်တာနက် ချန်နယ်လိုင်းများ", + "TasksApplicationCategory": "အပလီကေးရှင်း", + "TasksLibraryCategory": "မီဒီယာတိုက်", + "TasksMaintenanceCategory": "ပြုပြင် ထိန်းသိမ်းခြင်း", + "VersionNumber": "ဗားရှင်း {0}", + "ValueSpecialEpisodeName": "အထူး- {0}", + "ValueHasBeenAddedToLibrary": "{0} ကို သင့်မီဒီယာဒစ်ဂျစ်တိုက်သို့ ပေါင်းထည့်လိုက်ပါပြီ", + "UserStoppedPlayingItemWithValues": "{0} သည် {1} ကို {2} တွင် ဖွင့်ပြီးပါပြီ", + "UserStartedPlayingItemWithValues": "{0} သည် {1} ကို {2} တွင် ပြသနေသည်", + "UserPolicyUpdatedWithName": "{0} အတွက် အသုံးပြုသူမူဝါဒကို အပ်ဒိတ်လုပ်ပြီးပါပြီ", + "UserPasswordChangedWithName": "အသုံးပြုသူ {0} အတွက် စကားဝှက်ကို ပြောင်းထားသည်", + "UserOnlineFromDevice": "{0} သည် {1} မှ အွန်လိုင်းဖြစ်သည်", + "UserOfflineFromDevice": "{0} သည် {1} မှ ချိတ်ဆက်မှုပြတ်တောက်သွားသည်", + "UserLockedOutWithName": "အသုံးပြုသူ {0} အား လော့ခ်ချထားသည်", + "UserDownloadingItemWithValues": "{0} သည် {1} ကို ဒေါင်းလုဒ်လုပ်နေသည်", + "UserDeletedWithName": "အသုံးပြုသူ {0} ကို ဖျက်လိုက်ပါပြီ", + "UserCreatedWithName": "အသုံးပြုသူ {0} ကို ဖန်တီးပြီးပါပြီ", + "User": "အသုံးပြုသူ", + "Undefined": "သတ်မှတ်မထားသော", + "TvShows": "တီဗီ ဇာတ်လမ်းတွဲများ", + "System": "စနစ်", + "Sync": "ချိန်ကိုက်မည်", + "SubtitleDownloadFailureFromForItem": "{1} အတွက် {0} မှ စာတန်းထိုးများ ဒေါင်းလုဒ်လုပ်ခြင်း မအောင်မြင်ပါ", + "StartupEmbyServerIsLoading": "Jellyfin ဆာဗာကို အသင့်ပြင်နေပါသည်။ ခဏနေ ထပ်စမ်းကြည့်ပါ။", + "Songs": "သီချင်းများ", + "Shows": "ဇာတ်လမ်းတွဲများ", + "ServerNameNeedsToBeRestarted": "{0} ကို ပြန်လည်စတင်ရန် လိုအပ်သည်", + "ScheduledTaskStartedWithName": "{0} စတင်ခဲ့သည်", + "ScheduledTaskFailedWithName": "{0} မအောင်မြင်ပါ", + "ProviderValue": "ဝန်ဆောင်မှုပေးသူ- {0}", + "PluginUpdatedWithName": "ပလပ်ခ်အင် {0} ကို အပ်ဒိတ်လုပ်ထားသည်", + "PluginUninstalledWithName": "ပလပ်ခ်အင် {0} ကို ဖြုတ်လိုက်ပါပြီ", + "PluginInstalledWithName": "ပလပ်ခ်အင် {0} ကို ထည့်သွင်းခဲ့သည်", + "Plugin": "ပလပ်အင်", + "Playlists": "အစီအစဉ်များ", + "Photos": "ဓာတ်ပုံများ", + "NotificationOptionVideoPlaybackStopped": "ဗီဒီယိုဖွင့်ခြင်း ရပ်သွားသည်", + "NotificationOptionVideoPlayback": "ဗီဒီယိုဖွင့်ခြင်း စတင်ပါပြီ", + "NotificationOptionUserLockedOut": "အသုံးပြုသူ ဝင်ရန် တားမြစ်ခံရသည်", + "NotificationOptionTaskFailed": "စီစဉ်ထားသော အလုပ်ပျက်ကွက်", + "NotificationOptionServerRestartRequired": "ဆာဗာ ပြန်လည်စတင်ရန် လိုအပ်သည်", + "NotificationOptionPluginUpdateInstalled": "ပလပ်အင် အပ်ဒိတ် ထည့်သွင်းပြီးပါပြီ", + "NotificationOptionPluginUninstalled": "ပလပ်အင်ကို ဖြုတ်လိုက်ပါပြီ", + "NotificationOptionPluginInstalled": "ပလပ်အင် ထည့်သွင်းထားသည်", + "NotificationOptionPluginError": "ပလပ်အင် ချို့ယွင်းခြင်း", + "NotificationOptionNewLibraryContent": "အသစ်များ ထပ်ထည့်ထားပါတယ်", + "NotificationOptionInstallationFailed": "ထည့်သွင်းမှု မအောင်မြင်ပါ", + "NotificationOptionCameraImageUploaded": "ကင်မရာမှ ဓာတ်ပုံ အပ်လုဒ် ပြီးပါပြီ", + "NotificationOptionAudioPlaybackStopped": "အသံဖိုင်ဖွင့်ခြင်း ရပ်သွားသည်", + "NotificationOptionAudioPlayback": "အသံဖွင့်ခြင်း စတင်ပါပြီ", + "NotificationOptionApplicationUpdateInstalled": "အပလီကေးရှင်း အပ်ဒိတ်ကို ထည့်သွင်းထားသည်", + "NotificationOptionApplicationUpdateAvailable": "အပလီကေးရှင်း အပ်ဒိတ် ရနိုင်ပါပြီ", + "NewVersionIsAvailable": "Jellyfin Server ၏ ဗားရှင်းအသစ်ကို ဒေါင်းလုဒ်လုပ်နိုင်ပါပြီ။", + "NameSeasonUnknown": "ဇာတ်လမ်းတွဲ အပိုင်းမသိ", + "NameSeasonNumber": "ဇာတ်လမ်းတွဲ အပိုင်း {0}", + "NameInstallFailed": "{0} ထည့်သွင်းမှု မအောင်မြင်ပါ", + "MusicVideos": "ဂီတဗီဒီယိုများ", + "Music": "တေးဂီတ", + "Movies": "ရုပ်ရှင်များ", + "MixedContent": "ရောနှောပါဝင်မှု", + "MessageServerConfigurationUpdated": "ဆာဗာဖွဲ့စည်းပုံကို အပ်ဒိတ်လုပ်ပြီးပါပြီ", + "MessageNamedServerConfigurationUpdatedWithValue": "ဆာဗာဖွဲ့စည်းပုံကဏ္ဍ {0} ကို အပ်ဒိတ်လုပ်ပြီးပါပြီ", + "MessageApplicationUpdatedTo": "Jellyfin ဆာဗာကို {0} သို့ အပ်ဒိတ်လုပ်ထားသည်", + "MessageApplicationUpdated": "Jellyfin ဆာဗာကို အပ်ဒိတ်လုပ်ပြီးပါပြီ", + "Latest": "နောက်ဆုံး", + "LabelRunningTimeValue": "ကြာချိန် - {0}", + "LabelIpAddressValue": "IP လိပ်စာ- {0}", + "ItemRemovedWithName": "{0} ကို ဒစ်ဂျစ်တိုက်မှ ဖယ်ရှားခဲ့သည်", + "ItemAddedWithName": "{0} ကို စာကြည့်တိုက်သို့ ထည့်ထားသည်", + "Inherit": "ဆက်ခံ၍ လုပ်ဆောင်သည်", + "HomeVideos": "ကိုယ်တိုင်ရိုက် ဗီဒီယိုများ", + "HeaderRecordingGroups": "အသံဖမ်းအဖွဲ့များ", + "HeaderNextUp": "နောက်ထပ်", + "HeaderLiveTV": "တီဗွီတိုက်ရိုက်", + "HeaderFavoriteSongs": "အကြိုက်ဆုံးသီချင်းများ", + "HeaderFavoriteShows": "အကြိုက်ဆုံး ဇာတ်လမ်းတွဲများ", + "HeaderFavoriteEpisodes": "အကြိုက်ဆုံး ဇာတ်လမ်းအပိုင်းများ", + "HeaderFavoriteArtists": "အကြိုက်ဆုံး အနုပညာရှင်များ", + "HeaderFavoriteAlbums": "အကြိုက်ဆုံး အယ်လ်ဘမ်များ", + "HeaderContinueWatching": "ဆက်လက်ကြည့်ရှုပါ", + "HeaderAlbumArtists": "အယ်လ်ဘမ်အနုပညာရှင်များ", + "Genres": "အမျိုးအစားများ", + "Forced": "အတင်းအကြပ်", + "Folders": "ဖိုလ်ဒါများ", + "Favorites": "အကြိုက်ဆုံးများ", + "FailedLoginAttemptWithUserName": "{0} မှ အကောင့်ဝင်ရန် မအောင်မြင်ပါ", + "DeviceOnlineWithName": "{0} ကို ချိတ်ဆက်ထားသည်", + "DeviceOfflineWithName": "{0} နှင့် အဆက်ပြတ်သွားပါပြီ", + "ChapterNameValue": "အခန်း {0}", + "CameraImageUploadedFrom": "ကင်မရာပုံအသစ်ကို {0} မှ ထည့်သွင်းလိုက်သည်", + "AuthenticationSucceededWithUserName": "{0} အောင်မြင်စွာ စစ်မှန်ကြောင်း အတည်ပြုပြီးပါပြီ", + "Application": "အပလီကေးရှင်း", + "AppDeviceValues": "အက်ပ်- {0}၊ စက်- {1}", + "External": "ပြင်ပ", + "TaskKeyframeExtractorDescription": "ပိုမိုတိကျသည့် အိတ်ချ်အယ်လ်အက်စ် အစဉ်လိုက်ပြသမှုများ ဖန်တီးနိုင်ရန်အတွက် ဗီဒီယိုဖိုင်များမှ ကီးဖရိန်များကို ထုတ်နှုတ်ယူမည် ဖြစ်သည်။ ဤလုပ်ဆောင်မှုသည် အချိန်ကြာရှည်နိုင်သည်။", + "TaskCleanCollectionsAndPlaylistsDescription": "စုစည်းမှုများနှင့် အစဉ်လိုက်ပြသမှုများမှ မရှိတော့သည်များကို ဖယ်ရှားမည်။", + "TaskRefreshTrickplayImages": "ထရစ်ခ်ပလေး ပုံများကို ထုတ်မည်", + "TaskKeyframeExtractor": "ကီးဖရိန်များကို ထုတ်နုတ်ခြင်း", + "TaskCleanCollectionsAndPlaylists": "စုစည်းမှုများနှင့် အစဉ်လိုက်ပြသမှုများကို ရှင်းလင်းမည်", + "HearingImpaired": "အကြားအာရုံ ချို့တဲ့သူ", + "TaskDownloadMissingLyrics": "ကျန်နေသောသီချင်းစာသားများအား ဒေါင်းလုတ်ဆွဲပါ", + "TaskDownloadMissingLyricsDescription": "သီချင်းများအတွက် သီချင်းစာသား ဒေါင်းလုတ်ဆွဲပါ" +} diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json new file mode 100644 index 00000000..cd031572 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -0,0 +1,141 @@ +{ + "Albums": "Album", + "AppDeviceValues": "App: {0}, Enhet: {1}", + "Application": "Program", + "Artists": "Artister", + "AuthenticationSucceededWithUserName": "{0} har logget inn", + "Books": "Bøker", + "CameraImageUploadedFrom": "Et nytt kamerabilde har blitt lastet opp fra {0}", + "Channels": "Kanaler", + "ChapterNameValue": "Kapittel {0}", + "Collections": "Samlinger", + "DeviceOfflineWithName": "{0} har koblet fra", + "DeviceOnlineWithName": "{0} er tilkoblet", + "FailedLoginAttemptWithUserName": "Mislykket påloggingsforsøk fra {0}", + "Favorites": "Favoritter", + "Folders": "Mapper", + "Genres": "Sjangre", + "HeaderAlbumArtists": "Albumartister", + "HeaderContinueWatching": "Fortsett å se", + "HeaderFavoriteAlbums": "Favorittalbum", + "HeaderFavoriteArtists": "Favorittartister", + "HeaderFavoriteEpisodes": "Favorittepisoder", + "HeaderFavoriteShows": "Favorittserier", + "HeaderFavoriteSongs": "Favorittsanger", + "HeaderLiveTV": "Direkte-TV", + "HeaderNextUp": "Neste", + "HeaderRecordingGroups": "Opptaksgrupper", + "HomeVideos": "Hjemmelagde filmer", + "Inherit": "Arve", + "ItemAddedWithName": "{0} ble lagt til i biblioteket", + "ItemRemovedWithName": "{0} ble fjernet fra biblioteket", + "LabelIpAddressValue": "IP-adresse: {0}", + "LabelRunningTimeValue": "Spilletid: {0}", + "Latest": "Siste", + "MessageApplicationUpdated": "Jellyfin-serveren har blitt oppdatert", + "MessageApplicationUpdatedTo": "Jellyfin-serveren ble oppdatert til {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfigurasjonsseksjon {0} har blitt oppdatert", + "MessageServerConfigurationUpdated": "Serverkonfigurasjon har blitt oppdatert", + "MixedContent": "Blandet innhold", + "Movies": "Filmer", + "Music": "Musikk", + "MusicVideos": "Musikkvideoer", + "NameInstallFailed": "Installasjonen av {0} mislyktes", + "NameSeasonNumber": "Sesong {0}", + "NameSeasonUnknown": "Ukjent sesong", + "NewVersionIsAvailable": "En ny versjon av Jellyfin Server er tilgjengelig for nedlasting.", + "NotificationOptionApplicationUpdateAvailable": "En programvareoppdatering er tilgjengelig", + "NotificationOptionApplicationUpdateInstalled": "Applikasjonsoppdatering installert", + "NotificationOptionAudioPlayback": "Lydavspilling startet", + "NotificationOptionAudioPlaybackStopped": "Lydavspilling stoppet", + "NotificationOptionCameraImageUploaded": "Kamerabilde lastet opp", + "NotificationOptionInstallationFailed": "Installasjonsfeil", + "NotificationOptionNewLibraryContent": "Nytt innhold lagt til", + "NotificationOptionPluginError": "Programvareutvidelsesfeil", + "NotificationOptionPluginInstalled": "Programvareutvidelse installert", + "NotificationOptionPluginUninstalled": "Programvareutvidelse avinstallert", + "NotificationOptionPluginUpdateInstalled": "Programvareutvidelsesoppdatering installert", + "NotificationOptionServerRestartRequired": "Serveromstart er nødvendig", + "NotificationOptionTaskFailed": "Feil under utføring av planlagt oppgave", + "NotificationOptionUserLockedOut": "Bruker er utestengt", + "NotificationOptionVideoPlayback": "Videoavspilling startet", + "NotificationOptionVideoPlaybackStopped": "Videoavspilling stoppet", + "Photos": "Bilder", + "Playlists": "Spillelister", + "Plugin": "Programvareutvidelse", + "PluginInstalledWithName": "{0} ble installert", + "PluginUninstalledWithName": "{0} ble avinstallert", + "PluginUpdatedWithName": "{0} ble oppdatert", + "ProviderValue": "Leverandør: {0}", + "ScheduledTaskFailedWithName": "{0} mislykkes", + "ScheduledTaskStartedWithName": "{0} startet", + "ServerNameNeedsToBeRestarted": "{0} må startes på nytt", + "Shows": "Serier", + "Songs": "Sanger", + "StartupEmbyServerIsLoading": "Jellyfin Server laster. Prøv igjen snart.", + "SubtitleDownloadFailureFromForItem": "Kunne ikke laste ned undertekster fra {0} for {1}", + "Sync": "Synkroniser", + "System": "System", + "TvShows": "TV-serier", + "User": "Bruker", + "UserCreatedWithName": "Bruker {0} er opprettet", + "UserDeletedWithName": "Bruker {0} har blitt slettet", + "UserDownloadingItemWithValues": "{0} laster ned {1}", + "UserLockedOutWithName": "Bruker {0} er blitt utestengt", + "UserOfflineFromDevice": "{0} har koblet fra {1}", + "UserOnlineFromDevice": "{0} er tilkoblet fra {1}", + "UserPasswordChangedWithName": "Passordet for {0} er oppdatert", + "UserPolicyUpdatedWithName": "Brukerretningslinjene har blitt oppdatert for {0}", + "UserStartedPlayingItemWithValues": "{0} har startet avspilling {1} på {2}", + "UserStoppedPlayingItemWithValues": "{0} har stoppet avspilling {1}", + "ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt", + "ValueSpecialEpisodeName": "Spesialepisode - {0}", + "VersionNumber": "Versjon {0}", + "TasksChannelsCategory": "Internettkanaler", + "TasksApplicationCategory": "Applikasjon", + "TasksLibraryCategory": "Bibliotek", + "TasksMaintenanceCategory": "Vedlikehold", + "TaskCleanCache": "Tøm hurtigbuffer", + "TaskRefreshLibrary": "Skann mediebibliotek", + "TaskRefreshChapterImagesDescription": "Lager forhåndsvisningsbilder for videoer som har kapitler.", + "TaskRefreshChapterImages": "Trekk ut kapittelbilder", + "TaskCleanCacheDescription": "Sletter mellomlagrede filer som ikke lengre trengs av systemet.", + "TaskDownloadMissingSubtitlesDescription": "Søker etter manglende undertekster på nett basert på metadatakonfigurasjon.", + "TaskDownloadMissingSubtitles": "Last ned manglende undertekster", + "TaskRefreshChannelsDescription": "Oppdaterer internettkanalinformasjon.", + "TaskRefreshChannels": "Oppdater kanaler", + "TaskCleanTranscodeDescription": "Sletter omkodede filer som er mer enn én dag gamle.", + "TaskCleanTranscode": "Tøm transkodingmappe", + "TaskUpdatePluginsDescription": "Laster ned og installerer oppdateringer for programvareutvidelser som er stilt inn til å oppdatere automatisk.", + "TaskUpdatePlugins": "Oppdater programvareutvidelse", + "TaskRefreshPeopleDescription": "Oppdaterer metadata for skuespillere og regissører i mediebiblioteket ditt.", + "TaskRefreshPeople": "Oppdater personer", + "TaskCleanLogsDescription": "Sletter loggfiler som er eldre enn {0} dager gamle.", + "TaskCleanLogs": "Tøm loggmappe", + "TaskRefreshLibraryDescription": "Skanner mediebibliotekene dine for nye filer og oppdaterer metadata.", + "TaskCleanActivityLog": "Tøm aktivitetslogg", + "Undefined": "Udefinert", + "Forced": "Tvunget", + "Default": "Standard", + "TaskCleanActivityLogDescription": "Sletter oppføringer i aktivitetsloggen som er eldre enn den konfigurerte alderen.", + "TaskOptimizeDatabase": "Optimiser database", + "TaskOptimizeDatabaseDescription": "Komprimerer database og frigjør plass. Denne prosessen kan forbedre ytelsen etter skanning av bibliotek eller andre handlinger som fører til databaseendringer.", + "TaskKeyframeExtractorDescription": "Trekker ut nøkkelbilder fra videofiler for å skape mere nøyaktige HLS-spillelister. Denne oppgaven kan ta lang tid.", + "TaskKeyframeExtractor": "Nøkkelbilde-uttrekker", + "External": "Ekstern", + "HearingImpaired": "Hørselshemmet", + "TaskRefreshTrickplayImages": "Generer Trickplay bilder", + "TaskRefreshTrickplayImagesDescription": "Oppretter trickplay-forhåndsvisninger for videoer i aktiverte biblioteker.", + "TaskCleanCollectionsAndPlaylists": "Rydd kolleksjoner og spillelister", + "TaskAudioNormalization": "Lydnormalisering", + "TaskAudioNormalizationDescription": "Skan filer for lydnormaliserende data.", + "TaskCleanCollectionsAndPlaylistsDescription": "Fjerner elementer fra kolleksjoner og spillelister som ikke lengere finnes.", + "TaskDownloadMissingLyrics": "Last ned manglende tekster", + "TaskDownloadMissingLyricsDescription": "Last ned sangtekster", + "TaskExtractMediaSegments": "Skann mediasegment", + "TaskMoveTrickplayImages": "Migrer bildeplassering for Trickplay", + "TaskMoveTrickplayImagesDescription": "Flytter eksisterende Trickplay-filer i henhold til biblioteksinstillingene.", + "TaskExtractMediaSegmentsDescription": "Trekker ut eller henter mediasegmenter fra plugins som støtter MediaSegment.", + "CleanupUserDataTaskDescription": "Sletter all brukerdata (avspillings-status, favoritter osv.) fra innhold som har vært utilgjengelig i minst 90 dager.", + "CleanupUserDataTask": "Oppgave for opprydding av brukerdata" +} diff --git a/Emby.Server.Implementations/Localization/Core/ne.json b/Emby.Server.Implementations/Localization/Core/ne.json new file mode 100644 index 00000000..7c6b08fb --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ne.json @@ -0,0 +1,127 @@ +{ + "NotificationOptionUserLockedOut": "प्रयोगकर्ता प्रतिबन्धित", + "NotificationOptionTaskFailed": "निर्धारित कार्य विफलता", + "NotificationOptionServerRestartRequired": "सर्भर रिस्टार्ट आवाश्यक छ", + "NotificationOptionPluginUpdateInstalled": "प्लगइन अद्यावधिक स्थापना भयो", + "NotificationOptionPluginUninstalled": "प्लगइन विस्थापित", + "NotificationOptionPluginInstalled": "प्लगइन स्थापना भयो", + "NotificationOptionPluginError": "प्लगइन असफलता", + "NotificationOptionNewLibraryContent": "नयाँ सामग्री थपियो", + "NotificationOptionInstallationFailed": "स्थापना असफलता", + "NotificationOptionCameraImageUploaded": "क्यामेरा फोटो अपलोड गरियो", + "NotificationOptionAudioPlaybackStopped": "ध्वनि प्रक्षेपण रोकियो", + "NotificationOptionAudioPlayback": "ध्वनि प्रक्षेपण शुरू भयो", + "NotificationOptionApplicationUpdateInstalled": "अनुप्रयोग अद्यावधिक स्थापना भयो", + "NotificationOptionApplicationUpdateAvailable": "अनुप्रयोग अपडेट उपलब्ध छ", + "NewVersionIsAvailable": "जेलीफिन सर्भर को नयाँ संस्करण डाउनलोड को लागी उपलब्ध छ।", + "NameSeasonUnknown": "अज्ञात श्रृंखला", + "NameSeasonNumber": "श्रृंखला {0}", + "NameInstallFailed": "{0} स्थापना असफल भयो", + "MusicVideos": "सांगीतिक भिडियोहरू", + "Music": "संगीत", + "Movies": "चलचित्रहरू", + "MixedContent": "मिश्रित सामग्री", + "MessageServerConfigurationUpdated": "सर्भर कन्फिगरेसन अद्यावधिक गरिएको छ", + "MessageNamedServerConfigurationUpdatedWithValue": "सर्भर कन्फिगरेसन विभाग {0} अद्यावधिक गरिएको छ", + "MessageApplicationUpdatedTo": "जेलीफिन सर्भर {0} मा अद्यावधिक गरिएको छ", + "MessageApplicationUpdated": "जेलीफिन सर्भर अपडेट गरिएको छ", + "Latest": "नविनतम", + "LabelRunningTimeValue": "कुल समय: {0}", + "LabelIpAddressValue": "आईपी ठेगाना: {0}", + "ItemRemovedWithName": "{0}लाई पुस्तकालयबाट हटाईयो", + "ItemAddedWithName": "{0} लाईब्रेरीमा थपियो", + "Inherit": "उत्तराधिकार", + "HomeVideos": "घरेलु भिडियोहरू", + "HeaderRecordingGroups": "रेकर्ड समूहहरू", + "HeaderNextUp": "आगामी", + "HeaderLiveTV": "प्रत्यक्ष टिभी", + "HeaderFavoriteSongs": "मनपर्ने गीतहरू", + "HeaderFavoriteShows": "मनपर्ने कार्यक्रमहरू", + "HeaderFavoriteEpisodes": "मनपर्ने एपिसोडहरू", + "HeaderFavoriteArtists": "मनपर्ने कलाकारहरू", + "HeaderFavoriteAlbums": "मनपर्ने एल्बमहरू", + "HeaderContinueWatching": "हेर्न जारी राख्नुहोस्", + "HeaderAlbumArtists": "एल्बमका कलाकारहरू", + "Genres": "विधाहरू", + "Folders": "फोल्डरहरू", + "Favorites": "मनपर्ने", + "FailedLoginAttemptWithUserName": "{0}को लग इन प्रयास असफल", + "DeviceOnlineWithName": "{0}को साथ जडित", + "DeviceOfflineWithName": "{0}बाट विच्छेदन भयो", + "Collections": "संग्रह", + "ChapterNameValue": "अध्याय {0}", + "Channels": "च्यानलहरू", + "AppDeviceValues": "अनुप्रयोग: {0}, उपकरण: {1}", + "AuthenticationSucceededWithUserName": "{0} सफलतापूर्वक प्रमाणीकरण गरियो", + "CameraImageUploadedFrom": "{0}बाट नयाँ क्यामेरा छवि अपलोड गरिएको छ", + "Books": "पुस्तकहरु", + "Artists": "कलाकारहरू", + "Application": "अनुप्रयोगहरू", + "Albums": "एल्बमहरू", + "TasksLibraryCategory": "पुस्तकालय", + "TasksApplicationCategory": "अनुप्रयोग", + "TasksMaintenanceCategory": "मर्मत", + "UserPolicyUpdatedWithName": "प्रयोगकर्ता नीति को लागी अद्यावधिक गरिएको छ {0}", + "UserPasswordChangedWithName": "पासवर्ड प्रयोगकर्ताका लागि परिवर्तन गरिएको छ {0}", + "UserOnlineFromDevice": "{0} बाट अनलाइन छ {1}", + "UserOfflineFromDevice": "{0} बाट विच्छेदन भएको छ {1}", + "UserLockedOutWithName": "प्रयोगकर्ता {0} लक गरिएको छ", + "UserDeletedWithName": "प्रयोगकर्ता {0} हटाइएको छ", + "UserCreatedWithName": "प्रयोगकर्ता {0} सिर्जना गरिएको छ", + "User": "प्रयोगकर्ता", + "PluginInstalledWithName": "{0} सभएको थियो", + "StartupEmbyServerIsLoading": "Jellyfin सर्भर लोड हुँदैछ। कृपया छिट्टै फेरि प्रयास गर्नुहोस्।", + "Songs": "गीतहरू", + "Shows": "शोहरू", + "ServerNameNeedsToBeRestarted": "{0} लाई पुन: सुरु गर्नु पर्छ", + "ScheduledTaskStartedWithName": "{0} सुरु भयो", + "ScheduledTaskFailedWithName": "{0} असफल", + "ProviderValue": "प्रदायक: {0}", + "Plugin": "प्लगइनहरू", + "Playlists": "प्लेलिस्टहरू", + "Photos": "तस्बिरहरु", + "NotificationOptionVideoPlaybackStopped": "भिडियो प्लेब्याक रोकियो", + "NotificationOptionVideoPlayback": "भिडियो प्लेब्याक सुरु भयो", + "Forced": "जबरजस्ती", + "External": "बाह्य", + "Default": "पूर्वनिर्धारित", + "TaskRefreshPeople": "मानिसहरूलाई ताजा गर्नुहोस्", + "TaskCleanLogsDescription": "लग फाइलहरू मेटाउँछ जुन {0} दिन भन्दा पुराना छ।", + "TaskCleanLogs": "लग निर्देशिका सफा गनुहोस्", + "TaskRefreshLibraryDescription": "नयाँ फाइलहरूको लागि तपाइँको मिडिया लाइब्रेरी स्क्यान गर्दछ र मेटाडेटा रिफ्रेस गर्दछ।", + "TaskRefreshLibrary": "मिडिया लाइब्रेरी स्क्यान गर्नुहोस्", + "TaskRefreshChapterImagesDescription": "अध्यायहरू भएका भिडियोहरूको लागि थम्बनेलहरू सिर्जना गर्दछ।", + "TaskRefreshChapterImages": "अध्याय छविहरू निकाल्नुहोस्", + "TaskCleanCacheDescription": "प्रणालीलाई अब आवश्यक नपर्ने क्यास फाइलहरू मेटाउँछ।", + "TaskCleanCache": "क्यास डाइरेक्टरी सफा गर्नुहोस्", + "TaskCleanActivityLogDescription": "कन्फिगर गरिएको उमेर भन्दा पुरानो गतिविधि लग प्रविष्टिहरू मेटाउँछ।", + "TaskCleanActivityLog": "गतिविधि लग सफा गर्नुहोस्", + "TasksChannelsCategory": "इन्टरनेट च्यानलहरू", + "VersionNumber": "संस्करण {0}", + "ValueSpecialEpisodeName": "विशेष - {0}", + "ValueHasBeenAddedToLibrary": "{0} तपाईंको मिडिया लाइब्रेरीमा थपिएको छ", + "UserStoppedPlayingItemWithValues": "{2} मा {0} हेरिसकेको छ{1}", + "UserStartedPlayingItemWithValues": "{0} हेर्दै {1} मा {2}", + "UserDownloadingItemWithValues": "{0} डाउनलोड गर्दै छ {1}", + "Undefined": "अपरिभाषित", + "TvShows": "टेलिभिजन कार्यक्रमहरू", + "System": "प्रणाली", + "Sync": "समकालीन", + "SubtitleDownloadFailureFromForItem": "उपशीर्षकहरू {0} बाट {1} को लागि डाउनलोड गर्न असफल", + "PluginUpdatedWithName": "{0} अद्यावधिक गरिएको थियो", + "PluginUninstalledWithName": "{0} को स्थापना रद्द गरिएको थियो", + "HearingImpaired": "सुन्न नसक्ने", + "TaskUpdatePluginsDescription": "स्वचालित रूपमा अद्यावधिक गर्न कन्फिगर गरिएका प्लगइनहरूका लागि अद्यावधिकहरू डाउनलोड र स्थापना गर्दछ।", + "TaskCleanTranscode": "सफा ट्रान्सकोड निर्देशिका", + "TaskCleanTranscodeDescription": "एक दिन भन्दा पुराना ट्रान्सकोड फाइलहरू मेटाउँछ।", + "TaskRefreshChannels": "च्यानलहरू ताजा गर्नुहोस्", + "TaskDownloadMissingSubtitlesDescription": "मेटाडेटा कन्फिगरेसनमा आधारित हराइरहेको उपशीर्षकहरूको लागि इन्टरनेट खोज्छ।", + "TaskOptimizeDatabase": "डेटाबेस अप्टिमाइज गर्नुहोस्", + "TaskOptimizeDatabaseDescription": "डाटाबेस कम्प्याक्ट र खाली ठाउँ काट्छ। पुस्तकालय स्क्यान गरेपछि वा डाटाबेस परिमार्जनलाई संकेत गर्ने अन्य परिवर्तनहरू गरेपछि यो कार्य चलाउँदा कार्यसम्पादनमा सुधार हुन सक्छ।", + "TaskKeyframeExtractorDescription": "थप सटीक एचएलएस प्लेलिस्टहरू सिर्जना गर्न भिडियो फाइलहरूबाट कीफ्रेमहरू निकाल्छ। यो कार्य लामो समय सम्म चल्न सक्छ।", + "TaskUpdatePlugins": "प्लगइनहरू अपडेट गर्नुहोस्", + "TaskRefreshPeopleDescription": "तपाईंको मिडिया लाइब्रेरीमा अभिनेता र निर्देशकहरूको लागि मेटाडेटा अपडेट गर्दछ।", + "TaskRefreshChannelsDescription": "इन्टरनेट च्यानल जानकारी ताजा गर्दछ।", + "TaskDownloadMissingSubtitles": "छुटेका उपशीर्षकहरू डाउनलोड गर्नुहोस्", + "TaskKeyframeExtractor": "कीफ्रेम एक्स्ट्रक्टर" +} diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json new file mode 100644 index 00000000..534c64e9 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -0,0 +1,141 @@ +{ + "Albums": "Albums", + "AppDeviceValues": "App: {0}, Apparaat: {1}", + "Application": "Applicatie", + "Artists": "Artiesten", + "AuthenticationSucceededWithUserName": "{0} is succesvol geauthenticeerd", + "Books": "Boeken", + "CameraImageUploadedFrom": "Nieuwe camera-afbeelding toegevoegd vanaf {0}", + "Channels": "Kanalen", + "ChapterNameValue": "Hoofdstuk {0}", + "Collections": "Collecties", + "DeviceOfflineWithName": "Verbinding met {0} is verbroken", + "DeviceOnlineWithName": "{0} is verbonden", + "FailedLoginAttemptWithUserName": "Mislukte aanmeldpoging van {0}", + "Favorites": "Favorieten", + "Folders": "Mappen", + "Genres": "Genres", + "HeaderAlbumArtists": "Albumartiesten", + "HeaderContinueWatching": "Verderkijken", + "HeaderFavoriteAlbums": "Favoriete albums", + "HeaderFavoriteArtists": "Favoriete artiesten", + "HeaderFavoriteEpisodes": "Favoriete afleveringen", + "HeaderFavoriteShows": "Favoriete series", + "HeaderFavoriteSongs": "Favoriete nummers", + "HeaderLiveTV": "Live-tv", + "HeaderNextUp": "Als volgende", + "HeaderRecordingGroups": "Opnamegroepen", + "HomeVideos": "Homevideo's", + "Inherit": "Erven", + "ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek", + "ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek", + "LabelIpAddressValue": "IP-adres: {0}", + "LabelRunningTimeValue": "Looptijd: {0}", + "Latest": "Nieuwste", + "MessageApplicationUpdated": "Jellyfin Server is bijgewerkt", + "MessageApplicationUpdatedTo": "Jellyfin Server is bijgewerkt naar {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Sectie {0} van de serverconfiguratie is bijgewerkt", + "MessageServerConfigurationUpdated": "Serverconfiguratie is bijgewerkt", + "MixedContent": "Gemengde inhoud", + "Movies": "Films", + "Music": "Muziek", + "MusicVideos": "Muziekvideo's", + "NameInstallFailed": "{0} installatie mislukt", + "NameSeasonNumber": "Seizoen {0}", + "NameSeasonUnknown": "Seizoen onbekend", + "NewVersionIsAvailable": "Een nieuwe versie van Jellyfin Server is beschikbaar om te downloaden.", + "NotificationOptionApplicationUpdateAvailable": "Programma-update beschikbaar", + "NotificationOptionApplicationUpdateInstalled": "Programma-update geïnstalleerd", + "NotificationOptionAudioPlayback": "Muziek gestart", + "NotificationOptionAudioPlaybackStopped": "Muziek gestopt", + "NotificationOptionCameraImageUploaded": "Camera-afbeelding geüpload", + "NotificationOptionInstallationFailed": "Installatie mislukt", + "NotificationOptionNewLibraryContent": "Nieuwe inhoud toegevoegd", + "NotificationOptionPluginError": "Plug-in-fout", + "NotificationOptionPluginInstalled": "Plug-in geïnstalleerd", + "NotificationOptionPluginUninstalled": "Plug-in verwijderd", + "NotificationOptionPluginUpdateInstalled": "Plug-in-update geïnstalleerd", + "NotificationOptionServerRestartRequired": "Herstarten server vereist", + "NotificationOptionTaskFailed": "Geplande taak mislukt", + "NotificationOptionUserLockedOut": "Gebruiker buitengesloten", + "NotificationOptionVideoPlayback": "Afspelen van video gestart", + "NotificationOptionVideoPlaybackStopped": "Afspelen van video gestopt", + "Photos": "Foto's", + "Playlists": "Afspeellijsten", + "Plugin": "Plug-in", + "PluginInstalledWithName": "{0} is geïnstalleerd", + "PluginUninstalledWithName": "{0} is verwijderd", + "PluginUpdatedWithName": "{0} is bijgewerkt", + "ProviderValue": "Aanbieder: {0}", + "ScheduledTaskFailedWithName": "{0} is mislukt", + "ScheduledTaskStartedWithName": "{0} is gestart", + "ServerNameNeedsToBeRestarted": "{0} moet herstart worden", + "Shows": "Series", + "Songs": "Nummers", + "StartupEmbyServerIsLoading": "Jellyfin Server is aan het laden. Probeer het later opnieuw.", + "SubtitleDownloadFailureFromForItem": "Ondertiteling kon niet gedownload worden van {0} voor {1}", + "Sync": "Synchronisatie", + "System": "Systeem", + "TvShows": "Tv-series", + "User": "Gebruiker", + "UserCreatedWithName": "Gebruiker {0} is aangemaakt", + "UserDeletedWithName": "Gebruiker {0} is verwijderd", + "UserDownloadingItemWithValues": "{0} downloadt {1}", + "UserLockedOutWithName": "Gebruiker {0} is buitengesloten", + "UserOfflineFromDevice": "Verbinding van {0} via {1} is verbroken", + "UserOnlineFromDevice": "{0} is verbonden via {1}", + "UserPasswordChangedWithName": "Wachtwoord voor {0} is gewijzigd", + "UserPolicyUpdatedWithName": "Gebruikersbeleid gewijzigd voor {0}", + "UserStartedPlayingItemWithValues": "{0} speelt {1} af op {2}", + "UserStoppedPlayingItemWithValues": "{0} heeft afspelen van {1} gestopt op {2}", + "ValueHasBeenAddedToLibrary": "{0} is toegevoegd aan je mediabibliotheek", + "ValueSpecialEpisodeName": "Special - {0}", + "VersionNumber": "Versie {0}", + "TaskDownloadMissingSubtitlesDescription": "Zoekt op het internet naar ontbrekende ondertiteling gebaseerd op metadataconfiguratie.", + "TaskDownloadMissingSubtitles": "Ontbrekende ondertiteling downloaden", + "TaskRefreshChannelsDescription": "Vernieuwt informatie van internetkanalen.", + "TaskRefreshChannels": "Kanalen vernieuwen", + "TaskCleanTranscodeDescription": "Verwijdert transcoderingsbestanden ouder dan een dag.", + "TaskCleanLogs": "Logboekmap opschonen", + "TaskCleanTranscode": "Transcoderingsmap opschonen", + "TaskUpdatePluginsDescription": "Downloadt en installeert updates van plug-ins waarvoor automatisch bijwerken is ingeschakeld.", + "TaskUpdatePlugins": "Plug-ins bijwerken", + "TaskRefreshPeopleDescription": "Werkt metadata bij voor acteurs en regisseurs in je mediabibliotheek.", + "TaskRefreshPeople": "Personen vernieuwen", + "TaskCleanLogsDescription": "Verwijdert logboekbestanden ouder dan {0} dagen.", + "TaskRefreshLibraryDescription": "Scant de mediabibliotheek op nieuwe bestanden en vernieuwt de metadata.", + "TaskRefreshLibrary": "Mediabibliotheek scannen", + "TaskRefreshChapterImagesDescription": "Maakt voorbeeldafbeedingen aan voor video's met hoofdstukken.", + "TaskRefreshChapterImages": "Hoofdstukafbeeldingen uitpakken", + "TaskCleanCacheDescription": "Verwijdert gecachte bestanden die het systeem niet langer nodig heeft.", + "TaskCleanCache": "Cache-map opschonen", + "TasksChannelsCategory": "Internetkanalen", + "TasksApplicationCategory": "Toepassing", + "TasksLibraryCategory": "Bibliotheek", + "TasksMaintenanceCategory": "Onderhoud", + "TaskCleanActivityLogDescription": "Verwijdert activiteitenlogs ouder dan de ingestelde leeftijd.", + "TaskCleanActivityLog": "Activiteitenlogboek legen", + "Undefined": "Niet gedefinieerd", + "Forced": "Gedwongen", + "Default": "Standaard", + "TaskOptimizeDatabaseDescription": "Comprimeert de database en trimt vrije ruimte. Het uitvoeren van deze taak kan de prestaties verbeteren, na het scannen van de bibliotheek of andere aanpassingen die invloed hebben op de database.", + "TaskOptimizeDatabase": "Database optimaliseren", + "TaskKeyframeExtractorDescription": "Haalt keyframes uit videobestanden om preciezere HLS-afspeellijsten te maken. Deze taak kan lang duren.", + "TaskKeyframeExtractor": "Keyframes uitpakken", + "External": "Extern", + "HearingImpaired": "Slechthorenden", + "TaskRefreshTrickplayImages": "Trickplay-afbeeldingen genereren", + "TaskRefreshTrickplayImagesDescription": "Creëert trickplay-voorvertoningen voor video's in bibliotheken waarvoor dit is ingeschakeld.", + "TaskCleanCollectionsAndPlaylists": "Collecties en afspeellijsten opruimen", + "TaskCleanCollectionsAndPlaylistsDescription": "Verwijdert niet langer bestaande items uit collecties en afspeellijsten.", + "TaskAudioNormalization": "Geluidsnormalisatie", + "TaskAudioNormalizationDescription": "Scant bestanden op gegevens voor geluidsnormalisatie.", + "TaskDownloadMissingLyrics": "Ontbrekende liedteksten downloaden", + "TaskDownloadMissingLyricsDescription": "Downloadt liedteksten", + "TaskExtractMediaSegmentsDescription": "Verkrijgt mediasegmenten vanuit plug-ins met MediaSegment-ondersteuning.", + "TaskMoveTrickplayImages": "Locatie trickplay-afbeeldingen migreren", + "TaskMoveTrickplayImagesDescription": "Verplaatst bestaande trickplay-bestanden op basis van de bibliotheekinstellingen.", + "TaskExtractMediaSegments": "Scannen op mediasegmenten", + "CleanupUserDataTaskDescription": "Wist alle gebruikersgegevens (kijkstatus, favorieten, etc.) van media die al minstens 90 dagen niet meer aanwezig zijn.", + "CleanupUserDataTask": "Opruimtaak gebruikersdata" +} diff --git a/Emby.Server.Implementations/Localization/Core/nn.json b/Emby.Server.Implementations/Localization/Core/nn.json new file mode 100644 index 00000000..feb5fe21 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/nn.json @@ -0,0 +1,125 @@ +{ + "MessageServerConfigurationUpdated": "Tenarkonfigurasjonen har blitt oppdatert", + "MessageNamedServerConfigurationUpdatedWithValue": "Tenar konfigurasjon seksjon {0} har blitt oppdatert", + "MessageApplicationUpdatedTo": "Jellyfin-tenaren har blitt oppdatert til {0}", + "MessageApplicationUpdated": "Jellyfin-tenaren har blitt oppdatert", + "Latest": "Nyaste", + "LabelRunningTimeValue": "Speletid: {0}", + "LabelIpAddressValue": "IP-adresse: {0}", + "ItemRemovedWithName": "{0} vart fjerna frå biblioteket", + "ItemAddedWithName": "{0} vart lagt til i biblioteket", + "Inherit": "Arve", + "HomeVideos": "Heimevideoar", + "HeaderRecordingGroups": "Innspelingsgrupper", + "HeaderNextUp": "Neste", + "HeaderLiveTV": "Direkte TV", + "HeaderFavoriteSongs": "Favorittsongar", + "HeaderFavoriteShows": "Favorittseriar", + "HeaderFavoriteEpisodes": "Favorittepisodar", + "HeaderFavoriteArtists": "Favorittartistar", + "HeaderFavoriteAlbums": "Favorittalbum", + "HeaderContinueWatching": "Fortsett å sjå", + "HeaderAlbumArtists": "Albumartist", + "Genres": "Sjangrar", + "Folders": "Mapper", + "Favorites": "Favorittar", + "FailedLoginAttemptWithUserName": "Mislukka påloggingsforsøk frå {0}", + "DeviceOnlineWithName": "{0} er tilkopla", + "DeviceOfflineWithName": "{0} har kopla frå", + "Collections": "Samlingar", + "ChapterNameValue": "Kapittel {0}", + "Channels": "Kanalar", + "CameraImageUploadedFrom": "Eit nytt kamerabilete har blitt lasta opp frå {0}", + "Books": "Bøker", + "AuthenticationSucceededWithUserName": "{0} har logga inn", + "Artists": "Artistar", + "Application": "Program", + "AppDeviceValues": "App: {0}, Eining: {1}", + "Albums": "Album", + "NotificationOptionServerRestartRequired": "Tenaren krev omstart", + "NotificationOptionPluginUpdateInstalled": "Programvaretilleggoppdatering vart installert", + "NotificationOptionPluginUninstalled": "Programvaretillegg avinstallert", + "NotificationOptionPluginInstalled": "Programvaretillegg installert", + "NotificationOptionPluginError": "Programvaretillegg feila", + "NotificationOptionNewLibraryContent": "Nytt innhald er lagt til", + "NotificationOptionInstallationFailed": "Installasjonsfeil", + "NotificationOptionCameraImageUploaded": "Kamerabilde vart lasta opp", + "NotificationOptionAudioPlaybackStopped": "Lydavspilling stoppa", + "NotificationOptionAudioPlayback": "Lydavspilling påbyrja", + "NotificationOptionApplicationUpdateInstalled": "Applikasjonsoppdatering er installert", + "NotificationOptionApplicationUpdateAvailable": "Applikasjonsoppdatering er tilgjengeleg", + "NewVersionIsAvailable": "Ein ny versjon av Jellyfin-tjenaren er tilgjengeleg for nedlasting.", + "NameSeasonUnknown": "Ukjend sesong", + "NameSeasonNumber": "Sesong {0}", + "NameInstallFailed": "Installasjonen av {0} feila", + "MusicVideos": "Musikkvideoar", + "Music": "Musikk", + "Movies": "Filmar", + "MixedContent": "Blanda innhald", + "Sync": "Synkroniser", + "TaskDownloadMissingSubtitlesDescription": "Søk Internettet for manglande undertekstar basert på metadatainnstillingar.", + "TaskDownloadMissingSubtitles": "Last ned manglande undertekstar", + "TaskRefreshChannelsDescription": "Oppdater internettkanalinformasjon.", + "TaskRefreshChannels": "Oppdater kanalar", + "TaskCleanTranscodeDescription": "Slett transkodefiler som er meir enn ein dag gammal.", + "TaskCleanTranscode": "Fjern transkodemappe", + "TaskUpdatePluginsDescription": "Laster ned og installerer oppdateringar for programtillegg som er sette opp til å oppdaterast automatisk.", + "TaskUpdatePlugins": "Oppdaterer programvaretillegg", + "TaskRefreshPeopleDescription": "Oppdaterer metadata for skodespelarar og regissørar i mediebiblioteket ditt.", + "TaskRefreshPeople": "Oppdater personar", + "TaskCleanLogsDescription": "Slett loggfiler som er meir enn {0} dagar gamle.", + "TaskCleanLogs": "Slett loggmappa", + "TaskRefreshLibraryDescription": "Skannar mediebiblioteket ditt for nye filer og oppdaterer metadata.", + "TaskRefreshLibrary": "Skann mediebibliotek", + "TaskRefreshChapterImagesDescription": "Lager miniatyrbilete for videoar som har kapittel.", + "TaskRefreshChapterImages": "Trekk ut kapittelbilete", + "TaskCleanCacheDescription": "Sletter mellomlagra filer som ikkje lengre trengst av systemet.", + "TaskCleanCache": "Fjern hurtigbuffer", + "TasksChannelsCategory": "Internettkanalar", + "TasksApplicationCategory": "Applikasjon", + "TasksLibraryCategory": "Bibliotek", + "TasksMaintenanceCategory": "Vedlikehald", + "VersionNumber": "Versjon {0}", + "ValueSpecialEpisodeName": "Spesialepisode - {0}", + "ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt", + "UserStoppedPlayingItemWithValues": "{0} har fullført avspeling {1} på {2}", + "UserStartedPlayingItemWithValues": "{0} spelar {1} på {2}", + "UserPolicyUpdatedWithName": "Brukarreglar har blitt oppdatert for {0}", + "UserPasswordChangedWithName": "Passordet for {0} er oppdatert", + "UserOnlineFromDevice": "{0} er direktekopla frå {1}", + "UserOfflineFromDevice": "{0} har kopla frå {1}", + "UserLockedOutWithName": "Brukar {0} har blitt utestengd", + "UserDownloadingItemWithValues": "{0} lastar ned {1}", + "UserDeletedWithName": "Brukar {0} er sletta", + "UserCreatedWithName": "Brukar {0} er oppretta", + "User": "Brukar", + "TvShows": "TV-seriar", + "System": "System", + "SubtitleDownloadFailureFromForItem": "Feila å laste ned undertekstar frå {0} for {1}", + "StartupEmbyServerIsLoading": "Jellyfin-tenaren laster. Prøv igjen seinare.", + "Songs": "Sangar", + "Shows": "Seriar", + "ServerNameNeedsToBeRestarted": "{0} må omstartast", + "ScheduledTaskStartedWithName": "{0} starta", + "ScheduledTaskFailedWithName": "{0} feila", + "ProviderValue": "Leverandør: {0}", + "PluginUpdatedWithName": "{0} blei oppdatert", + "PluginUninstalledWithName": "{0} blei avinstallert", + "PluginInstalledWithName": "{0} blei installert", + "Plugin": "Programvaretillegg", + "Playlists": "Spelelister", + "Photos": "Bilete", + "NotificationOptionVideoPlaybackStopped": "Videoavspeling stoppa", + "NotificationOptionVideoPlayback": "Videoavspeling starta", + "NotificationOptionUserLockedOut": "Brukar er utestengd", + "NotificationOptionTaskFailed": "Planlagt oppgåve feila", + "TaskCleanActivityLogDescription": "Sletter aktivitetslogginnlegg som er eldre enn den konfigurerte alderen.", + "TaskCleanActivityLog": "Slett aktivitetslogg", + "Undefined": "Udefinert", + "Forced": "Tvungen", + "Default": "Standard", + "External": "Ekstern", + "HearingImpaired": "Nedsett høyrsel", + "TaskRefreshTrickplayImages": "Generer Trickplay-bilete", + "TaskAudioNormalization": "Normalisering av lyd" +} diff --git a/Emby.Server.Implementations/Localization/Core/oc.json b/Emby.Server.Implementations/Localization/Core/oc.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/oc.json @@ -0,0 +1 @@ +{} diff --git a/Emby.Server.Implementations/Localization/Core/or.json b/Emby.Server.Implementations/Localization/Core/or.json new file mode 100644 index 00000000..8251c129 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/or.json @@ -0,0 +1,12 @@ +{ + "External": "ବହିଃସ୍ଥ", + "Genres": "ଧରଣ", + "Albums": "ଆଲବମଗୁଡ଼ିକ", + "Artists": "କଳାକାରଗୁଡ଼ିକ", + "Application": "ଆପ୍ଲିକେସନ", + "Books": "ବହିଗୁଡ଼ିକ", + "Channels": "ଚ୍ୟାନେଲଗୁଡ଼ିକ", + "ChapterNameValue": "ବିଭାଗ {0}", + "Collections": "ସଂଗ୍ରହଗୁଡ଼ିକ", + "Folders": "ଫୋଲ୍ଡରଗୁଡ଼ିକ" +} diff --git a/Emby.Server.Implementations/Localization/Core/pa.json b/Emby.Server.Implementations/Localization/Core/pa.json new file mode 100644 index 00000000..ced9204b --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/pa.json @@ -0,0 +1,141 @@ +{ + "TaskRefreshChapterImages": "ਐਕਸਟਰੈਕਟ ਚੈਪਟਰ ਚਿੱਤਰ", + "TaskDownloadMissingSubtitlesDescription": "ਮੈਟਾਡੇਟਾ ਕੌਂਫਿਗਰੇਸ਼ਨ ਦੇ ਅਧਾਰ ਤੇ ਗਾਇਬ ਉਪਸਿਰਲੇਖਾਂ ਲਈ ਇੰਟਰਨੈਟ ਦੀ ਭਾਲ ਕਰਦਾ ਹੈ.", + "TaskDownloadMissingSubtitles": "ਗਾਇਬ ਉਪਸਿਰਲੇਖ ਡਾ Download ਨਲੋਡ ਕਰੋ", + "TaskRefreshChannelsDescription": "ਇੰਟਰਨੈੱਟ ਚੈਨਲ ਦੀ ਜਾਣਕਾਰੀ ਨੂੰ ਤਾਜ਼ਾ ਕਰਦਾ ਹੈ.", + "TaskRefreshChannels": "ਚੈਨਲਾਂ ਨੂੰ ਤਾਜ਼ਾ ਕਰੋ", + "TaskCleanTranscodeDescription": "ਇੱਕ ਦਿਨ ਤੋਂ ਵੱਧ ਪੁਰਾਣੀ ਟ੍ਰਾਂਸਕੋਡ ਫਾਈਲਾਂ ਨੂੰ ਮਿਟਾਉਂਦਾ ਹੈ.", + "TaskCleanTranscode": "ਕਲੀਨ ਟ੍ਰਾਂਸਕੋਡ ਡਾਇਰੈਕਟਰੀ", + "TaskUpdatePluginsDescription": "ਪਲਗਇੰਸਾਂ ਲਈ ਡਾਉਨਲੋਡ ਅਤੇ ਸਥਾਪਨਾ ਅਪਡੇਟਾਂ ਜੋ ਆਪਣੇ ਆਪ ਅਪਡੇਟ ਕਰਨ ਲਈ ਕੌਂਫਿਗਰ ਕੀਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ.", + "TaskUpdatePlugins": "ਪਲੱਗਇਨ ਅਪਡੇਟ ਕਰੋ", + "TaskRefreshPeopleDescription": "ਤੁਹਾਡੀ ਮੀਡੀਆ ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚ ਅਦਾਕਾਰਾਂ ਅਤੇ ਨਿਰਦੇਸ਼ਕਾਂ ਲਈ ਮੈਟਾਡੇਟਾ ਨੂੰ ਅਪਡੇਟ ਕਰਦਾ ਹੈ.", + "TaskRefreshPeople": "ਲੋਕਾਂ ਨੂੰ ਤਾਜ਼ਾ ਕਰੋ", + "TaskCleanLogsDescription": "ਲੌਗ ਫਾਈਲਾਂ ਨੂੰ ਮਿਟਾਉਂਦਾ ਹੈ ਜੋ {0} ਦਿਨਾਂ ਤੋਂ ਵੱਧ ਪੁਰਾਣੀਆਂ ਹਨ.", + "TaskCleanLogs": "ਕਲੀਨ ਲਾਗ ਡਾਇਰੈਕਟਰੀ", + "TaskRefreshLibraryDescription": "ਨਵੀਆਂ ਫਾਈਲਾਂ ਲਈ ਆਪਣੀ ਮੀਡੀਆ ਲਾਇਬ੍ਰੇਰੀ ਨੂੰ ਸਕੈਨ ਕਰਦਾ ਹੈ ਅਤੇ ਮੈਟਾਡੇਟਾ ਨੂੰ ਤਾਜ਼ਾ ਕਰਦਾ ਹੈ.", + "TaskRefreshLibrary": "ਸਕੈਨ ਮੀਡੀਆ ਲਾਇਬ੍ਰੇਰੀ", + "TaskRefreshChapterImagesDescription": "ਚੈਪਟਰਾਂ ਵਾਲੇ ਵੀਡੀਓ ਲਈ ਥੰਬਨੇਲ ਬਣਾਉਂਦੇ ਹਨ.", + "TaskCleanCacheDescription": "ਸਿਸਟਮ ਦੁਆਰਾ ਹੁਣ ਕੈਚੇ ਫਾਈਲਾਂ ਦੀ ਜਰੂਰਤ ਨਹੀਂ ਹੈ.", + "TaskCleanCache": "ਸਾਫ਼ ਕੈਸ਼ ਡਾਇਰੈਕਟਰੀ", + "TaskCleanActivityLogDescription": "ਕੌਂਫਿਗਰ ਕੀਤੀ ਉਮਰ ਤੋਂ ਪੁਰਾਣੀ ਗਤੀਵਿਧੀ ਲੌਗ ਐਂਟਰੀਜ ਨੂੰ ਮਿਟਾਉਂਦਾ ਹੈ.", + "TaskCleanActivityLog": "ਸਾਫ਼ ਗਤੀਵਿਧੀ ਲਾਗ", + "TasksChannelsCategory": "ਇੰਟਰਨੈੱਟ ਚੈਨਲ", + "TasksApplicationCategory": "ਐਪਲੀਕੇਸ਼ਨ", + "TasksLibraryCategory": "ਲਾਇਬ੍ਰੇਰੀ", + "TasksMaintenanceCategory": "ਰੱਖ-ਰਖਾਅ", + "VersionNumber": "ਵਰਜਨ {0}", + "ValueSpecialEpisodeName": "ਖਾਸ - {0}", + "ValueHasBeenAddedToLibrary": "{0} ਤੁਹਾਡੀ ਮੀਡੀਆ ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ ਹੈ", + "UserStoppedPlayingItemWithValues": "{0} ਨੇ {2} 'ਤੇ {1} ਖੇਡਣਾ ਪੂਰਾ ਕਰ ਲਿਆ ਹੈ", + "UserStartedPlayingItemWithValues": "{0} {2} 'ਤੇ {1} ਖੇਡ ਰਿਹਾ ਹੈ", + "UserPolicyUpdatedWithName": "ਵਰਤੋਂਕਾਰ ਨੀਤੀ ਨੂੰ {0} ਲਈ ਅਪਡੇਟ ਕੀਤਾ ਗਿਆ ਹੈ", + "UserPasswordChangedWithName": "{0} ਵਰਤੋਂਕਾਰ ਲਈ ਪਾਸਵਰਡ ਬਦਲਿਆ ਗਿਆ ਸੀ", + "UserOnlineFromDevice": "{0} ਨੂੰ {1} ਤੋਂ ਆਨਲਾਈਨ ਹੈ", + "UserOfflineFromDevice": "{0} ਤੋਂ ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ ਹੈ {1}", + "UserLockedOutWithName": "ਵਰਤੋਂਕਾਰ {0} ਨੂੰ ਲਾਕ ਕੀਤਾ ਗਿਆ ਹੈ", + "UserDownloadingItemWithValues": "{0} {1} ਨੂੰ ਡਾਊਨਲੋਡ ਕਰ ਰਿਹਾ ਹੈ", + "UserDeletedWithName": "ਵਰਤੋਂਕਾਰ {0} ਨੂੰ ਹਟਾਇਆ ਗਿਆ", + "UserCreatedWithName": "ਵਰਤੋਂਕਾਰ {0} ਬਣਾਇਆ ਗਿਆ ਹੈ", + "User": "ਵਰਤੋਂਕਾਰ", + "Undefined": "ਪਰਿਭਾਸ਼ਤ", + "TvShows": "ਟੀਵੀ ਸ਼ੋਅ", + "System": "ਸਿਸਟਮ", + "Sync": "ਸਿੰਕ", + "SubtitleDownloadFailureFromForItem": "ਉਪਸਿਰਲੇਖ {1} ਲਈ {0} ਤੋਂ ਡਾਊਨਲੋਡ ਕਰਨ ਵਿੱਚ ਅਸਫਲ ਰਹੇ", + "StartupEmbyServerIsLoading": "Jellyfin ਸਰਵਰ ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ। ਛੇਤੀ ਹੀ ਫ਼ੇਰ ਕੋਸ਼ਿਸ਼ ਕਰੋ।", + "Songs": "ਗਾਣੇ", + "Shows": "ਸ਼ੋਅ", + "ServerNameNeedsToBeRestarted": "{0} ਮੁੜ ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ", + "ScheduledTaskStartedWithName": "{0} ਸ਼ੁਰੂ ਹੋਇਆ", + "ScheduledTaskFailedWithName": "{0} ਅਸਫਲ", + "ProviderValue": "ਦੇਣ ਵਾਲੇ: {0}", + "PluginUpdatedWithName": "{0} ਅਪਡੇਟ ਕੀਤਾ ਗਿਆ ਸੀ", + "PluginUninstalledWithName": "{0} ਅਣਇੰਸਟੌਲ ਕੀਤਾ ਗਿਆ ਸੀ", + "PluginInstalledWithName": "{0} ਲਗਾਇਆ ਗਿਆ ਸੀ", + "Plugin": "ਪਲੱਗਇਨ", + "Playlists": "ਪਲੇਸੂਚੀਆਂ", + "Photos": "ਫੋਟੋਆਂ", + "NotificationOptionVideoPlaybackStopped": "ਵੀਡੀਓ ਪਲੇਬੈਕ ਰੋਕਿਆ ਗਿਆ", + "NotificationOptionVideoPlayback": "ਵੀਡੀਓ ਪਲੇਬੈਕ ਸ਼ੁਰੂ ਹੋਇਆ", + "NotificationOptionUserLockedOut": "ਵਰਤੋਂਕਾਰ ਨੂੰ ਲਾਕ ਕੀਤਾ", + "NotificationOptionTaskFailed": "ਨਿਰਧਾਰਤ ਕਾਰਜ ਅਸਫਲਤਾ", + "NotificationOptionServerRestartRequired": "ਸਰਵਰ ਨੂੰ ਮੁੜ ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ", + "NotificationOptionPluginUpdateInstalled": "ਪਲੱਗਇਨ ਅਪਡੇਟ ਇੰਸਟੌਲ ਕੀਤਾ ਗਿਆ", + "NotificationOptionPluginUninstalled": "ਪਲੱਗਇਨ ਅਣਇੰਸਟੌਲ ਕੀਤਾ", + "NotificationOptionPluginInstalled": "ਪਲੱਗਇਨ ਇੰਸਟਾਲ ਕੀਤੀ", + "NotificationOptionPluginError": "ਪਲੱਗਇਨ ਅਸਫਲ", + "NotificationOptionNewLibraryContent": "ਨਵੀਂ ਸਮੱਗਰੀ ਸ਼ਾਮਲ ਕੀਤੀ ਗਈ", + "NotificationOptionInstallationFailed": "ਇੰਸਟਾਲੇਸ਼ਨ ਅਸਫਲ", + "NotificationOptionCameraImageUploaded": "ਕੈਮਰਾ ਤਸਵੀਰ ਅਪਲੋਡ ਕੀਤੀ ਗਈ", + "NotificationOptionAudioPlaybackStopped": "ਆਡੀਓ ਪਲੇਅਬੈਕ ਰੋਕਿਆ ਗਿਆ", + "NotificationOptionAudioPlayback": "ਆਡੀਓ ਪਲੇਅਬੈਕ ਸ਼ੁਰੂ ਹੋਇਆ", + "NotificationOptionApplicationUpdateInstalled": "ਐਪਲੀਕੇਸ਼ਨ ਅਪਡੇਟ ਇੰਸਟੌਲ ਕੀਤਾ ਗਿਆ", + "NotificationOptionApplicationUpdateAvailable": "ਐਪਲੀਕੇਸ਼ਨ ਅਪਡੇਟ ਉਪਲਬਧ ਹੈ", + "NewVersionIsAvailable": "ਜੈਲੀਫਿਨ ਸਰਵਰ ਦਾ ਨਵਾਂ ਸੰਸਕਰਣ ਡਾਉਨਲੋਡ ਲਈ ਉਪਲਬਧ ਹੈ.", + "NameSeasonUnknown": "ਸੀਜ਼ਨ ਅਣਜਾਣ", + "NameSeasonNumber": "ਸੀਜ਼ਨ {0}", + "NameInstallFailed": "{0} ਇੰਸਟਾਲੇਸ਼ਨ ਫੇਲ੍ਹ ਹੋਈ", + "MusicVideos": "ਸੰਗੀਤ ਵੀਡੀਓ", + "Music": "ਸੰਗੀਤ", + "Movies": "ਫਿਲਮਾਂ", + "MixedContent": "ਮਿਸ਼ਰਤ ਸਮੱਗਰੀ", + "MessageServerConfigurationUpdated": "ਸਰਵਰ ਕੌਂਫਿਗਰੇਸ਼ਨ ਨੂੰ ਅਪਡੇਟ ਕੀਤਾ ਗਿਆ ਹੈ", + "MessageNamedServerConfigurationUpdatedWithValue": "ਸਰਵਰ ਕੌਂਫਿਗਰੇਸ਼ਨ ਸੈਕਸ਼ਨ {0} ਅਪਡੇਟ ਕੀਤਾ ਗਿਆ ਹੈ", + "MessageApplicationUpdatedTo": "ਜੈਲੀਫਿਨ ਸਰਵਰ ਨੂੰ ਅੱਪਡੇਟ ਕੀਤਾ ਗਿਆ ਹੈ {0}", + "MessageApplicationUpdated": "ਜੈਲੀਫਿਨ ਸਰਵਰ ਅਪਡੇਟ ਕੀਤਾ ਗਿਆ ਹੈ", + "Latest": "ਤਾਜ਼ਾ", + "LabelRunningTimeValue": "ਚੱਲਦਾ ਸਮਾਂ: {0}", + "LabelIpAddressValue": "IP ਪਤਾ: {0}", + "ItemRemovedWithName": "{0} ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚੋਂ ਹਟਾ ਦਿੱਤਾ ਗਿਆ ਸੀ", + "ItemAddedWithName": "{0} ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ ਸੀ", + "Inherit": "ਵਿਰਾਸਤ", + "HomeVideos": "ਘਰੇਲੂ ਵੀਡੀਓ", + "HeaderRecordingGroups": "ਰਿਕਾਰਡਿੰਗ ਸਮੂਹ", + "HeaderNextUp": "ਅੱਗੇ", + "HeaderLiveTV": "ਲਾਈਵ ਟੀਵੀ", + "HeaderFavoriteSongs": "ਮਨਪਸੰਦ ਗਾਣੇ", + "HeaderFavoriteShows": "ਮਨਪਸੰਦ ਸ਼ੋਅ", + "HeaderFavoriteEpisodes": "ਮਨਪਸੰਦ ਐਪੀਸੋਡ", + "HeaderFavoriteArtists": "ਮਨਪਸੰਦ ਕਲਾਕਾਰ", + "HeaderFavoriteAlbums": "ਮਨਪਸੰਦ ਐਲਬਮ", + "HeaderContinueWatching": "ਵੇਖਣਾ ਜਾਰੀ ਰੱਖੋ", + "HeaderAlbumArtists": "ਐਲਬਮ ਕਲਾਕਾਰ", + "Genres": "ਸ਼ੈਲੀਆਂ", + "Forced": "ਮਜਬੂਰ", + "Folders": "ਫੋਲਡਰ", + "Favorites": "ਮਨਪਸੰਦ", + "FailedLoginAttemptWithUserName": "{0} ਤੋਂ ਲਾਗਇਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਫੇਲ ਹੋਈ", + "DeviceOnlineWithName": "{0} ਜੁੜਿਆ ਹੋਇਆ ਹੈ", + "DeviceOfflineWithName": "{0} ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ ਹੈ", + "Default": "ਡਿਫੌਲਟ", + "Collections": "ਸੰਗ੍ਰਹਿਣ", + "ChapterNameValue": "ਚੈਪਟਰ {0}", + "Channels": "ਚੈਨਲ", + "CameraImageUploadedFrom": "{0} ਤੋਂ ਇੱਕ ਨਵਾਂ ਕੈਮਰਾ ਚਿੱਤਰ ਅਪਲੋਡ ਕੀਤਾ ਗਿਆ ਹੈ", + "Books": "ਕਿਤਾਬਾਂ", + "AuthenticationSucceededWithUserName": "{0} ਸਫਲਤਾਪੂਰਕ ਪ੍ਰਮਾਣਿਤ", + "Artists": "ਕਲਾਕਾਰ", + "Application": "ਐਪਲੀਕੇਸ਼ਨ", + "AppDeviceValues": "ਐਪ: {0}, ਜੰਤਰ: {1}", + "Albums": "ਐਲਬਮਾਂ", + "TaskOptimizeDatabase": "ਡਾਟਾਬੇਸ ਅਨੁਕੂਲ ਬਣਾਓ", + "External": "ਬਾਹਰੀ", + "HearingImpaired": "ਸੁਨਣ ਵਿਚ ਕਮਜ਼ੋਰ", + "TaskAudioNormalizationDescription": "ਆਵਾਜ਼ ਸਧਾਰਣੀਕਰਨ ਡਾਟਾ ਲਈ ਫਾਇਲਾਂ ਖੋਜੋ।", + "TaskRefreshTrickplayImages": "ਟ੍ਰਿਕਪਲੇ ਤਸਵੀਰਾਂ ਤਿਆਰ ਕਰੋ", + "TaskExtractMediaSegments": "ਮੀਡੀਆ ਸੈਗਮੈਂਟ ਸਕੈਨ", + "TaskMoveTrickplayImagesDescription": "ਟ੍ਰਿਕਪਲੇ ਤਸਵੀਰਾਂ ਦੀ ਜਗਾ ਨੂੰ ਲਾਇਬ੍ਰੇਰੀ ਸੈਟਿੰਗਜ਼ ਅਨੁਸਾਰ ਬਦਲੋ।", + "TaskOptimizeDatabaseDescription": "ਡੇਟਾਬੇਸ ਨੂੰ ਸੰਗ੍ਰਹਿਤ ਕਰਦਾ ਹੈ ਅਤੇ ਖਾਲੀ ਜਗ੍ਹਾ ਘਟਾਉਂਦਾ ਹੈ। ਲਾਇਬ੍ਰੇਰੀ ਸਕੈਨ ਕਰਨ ਜਾਂ ਡੇਟਾਬੇਸ ਵਿੱਚ ਸੋਧਾਂ ਕਰਨ ਤੋਂ ਬਾਅਦ ਇਸ ਕੰਮ ਨੂੰ ਚਲਾਉਣਾ ਪ੍ਰਦਰਸ਼ਨ ਵਿੱਚ ਸੁਧਾਰ ਕਰ ਸਕਦਾ ਹੈ।", + "TaskExtractMediaSegmentsDescription": "ਮੀਡੀਆ ਸੈਗਮੈਂਟ ਨੂੰ ਮੀਡੀਆਸੈਗਮੈਂਟ ਯੋਗ ਪਲੱਗਇਨਾਂ ਤੋਂ ਨਿਕਾਲਦਾ ਜਾਂ ਪ੍ਰਾਪਤ ਕਰਦਾ ਹੈ।", + "TaskMoveTrickplayImages": "ਟ੍ਰਿਕਪਲੇ ਤਸਵੀਰਾਂ ਦੀ ਜਗਾ ਬਦਲੋ", + "TaskDownloadMissingLyrics": "ਅਧੂਰੇ ਬੋਲ ਡਾਊਨਲੋਡ ਕਰੋ", + "TaskDownloadMissingLyricsDescription": "ਗੀਤਾਂ ਲਈ ਡਾਊਨਲੋਡ ਕਿਤੇ ਬੋਲ", + "TaskKeyframeExtractor": "ਕੀ-ਫ੍ਰੇਮ ਐਕਸਟ੍ਰੈਕਟਰ", + "TaskCleanCollectionsAndPlaylistsDescription": "ਕਲੈਕਸ਼ਨਾਂ ਅਤੇ ਪਲੇਲਿਸਟਾਂ ਵਿੱਚੋਂ ਉਹ ਆਈਟਮ ਹਟਾਉਂਦਾ ਹੈ ਜੋ ਹੁਣ ਮੌਜੂਦ ਨਹੀਂ ਹਨ।", + "TaskCleanCollectionsAndPlaylists": "ਕਲੈਕਸ਼ਨਾਂ ਅਤੇ ਪਲੇਲਿਸਟਾਂ ਨੂੰ ਸਾਫ ਕਰੋ", + "TaskAudioNormalization": "ਆਵਾਜ਼ ਸਧਾਰਣੀਕਰਨ", + "TaskRefreshTrickplayImagesDescription": "ਵੀਡੀਓ ਲਈ ਟ੍ਰਿਕਪਲੇ ਪ੍ਰੀਵਿਊ ਬਣਾਉਂਦਾ ਹੈ (ਜੇ ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚ ਚੁਣਿਆ ਗਿਆ ਹੈ)।", + "TaskKeyframeExtractorDescription": "ਕੀ-ਫ੍ਰੇਮਜ਼ ਨੂੰ ਵੀਡੀਓ ਫਾਈਲਾਂ ਵਿੱਚੋਂ ਨਿਕਾਲਦਾ ਹੈ ਤਾਂ ਜੋ ਹੋਰ ਜ਼ਿਆਦਾ ਸਟਿਕ ਹੋਣ ਵਾਲੀਆਂ HLS ਪਲੇਲਿਸਟਾਂ ਬਣਾਈਆਂ ਜਾ ਸਕਣ। ਇਹ ਕੰਮ ਲੰਬੇ ਸਮੇਂ ਤੱਕ ਚੱਲ ਸਕਦਾ ਹੈ।", + "CleanupUserDataTaskDescription": "ਘੱਟੋ-ਘੱਟ 90 ਦਿਨਾਂ ਤੋਂ ਮੌਜੂਦ ਨਾ ਹੋਣ ਵਾਲੇ ਮੀਡੀਆ ਤੋਂ ਸਾਰੇ ਉਪਭੋਗਤਾ ਡੇਟਾ (ਵਾਚ ਸਟੇਟ, ਮਨਪਸੰਦ ਸਟੇਟਸ ਆਦਿ) ਨੂੰ ਸਾਫ਼ ਕਰਦਾ ਹੈ।", + "CleanupUserDataTask": "ਯੂਜ਼ਰ ਡਾਟਾ ਸਾਫ਼ ਕਰਨ ਦਾ ਕੰਮ" +} diff --git a/Emby.Server.Implementations/Localization/Core/pl.json b/Emby.Server.Implementations/Localization/Core/pl.json new file mode 100644 index 00000000..f1c19ac1 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/pl.json @@ -0,0 +1,141 @@ +{ + "Albums": "Albumy", + "AppDeviceValues": "Aplikacja: {0}, Urządzenie: {1}", + "Application": "Aplikacja", + "Artists": "Wykonawcy", + "AuthenticationSucceededWithUserName": "{0} został pomyślnie uwierzytelniony", + "Books": "Książki", + "CameraImageUploadedFrom": "Nowy obraz został przekazany z {0}", + "Channels": "Kanały", + "ChapterNameValue": "Rozdział {0}", + "Collections": "Kolekcje", + "DeviceOfflineWithName": "{0} został rozłączony", + "DeviceOnlineWithName": "{0} połączył się", + "FailedLoginAttemptWithUserName": "Nieudana próba logowania przez {0}", + "Favorites": "Ulubione", + "Folders": "Foldery", + "Genres": "Gatunki", + "HeaderAlbumArtists": "Wykonawcy albumów", + "HeaderContinueWatching": "Kontynuuj odtwarzanie", + "HeaderFavoriteAlbums": "Ulubione albumy", + "HeaderFavoriteArtists": "Ulubieni wykonawcy", + "HeaderFavoriteEpisodes": "Ulubione odcinki", + "HeaderFavoriteShows": "Ulubione seriale", + "HeaderFavoriteSongs": "Ulubione utwory", + "HeaderLiveTV": "Telewizja", + "HeaderNextUp": "Do obejrzenia", + "HeaderRecordingGroups": "Grupy nagrań", + "HomeVideos": "Nagrania domowe", + "Inherit": "Dziedzicz", + "ItemAddedWithName": "{0} zostało dodane do biblioteki", + "ItemRemovedWithName": "{0} zostało usunięte z biblioteki", + "LabelIpAddressValue": "Adres IP: {0}", + "LabelRunningTimeValue": "Czas trwania: {0}", + "Latest": "Ostatnio dodane", + "MessageApplicationUpdated": "Serwer Jellyfin został zaktualizowany", + "MessageApplicationUpdatedTo": "Serwer Jellyfin został zaktualizowany do wersji {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Sekcja {0} konfiguracji serwera została zaktualizowana", + "MessageServerConfigurationUpdated": "Konfiguracja serwera została zaktualizowana", + "MixedContent": "Zawartość mieszana", + "Movies": "Filmy", + "Music": "Muzyka", + "MusicVideos": "Teledyski", + "NameInstallFailed": "Instalacja {0} nieudana", + "NameSeasonNumber": "Sezon {0}", + "NameSeasonUnknown": "Nieznany sezon", + "NewVersionIsAvailable": "Nowa wersja serwera Jellyfin jest dostępna do pobrania.", + "NotificationOptionApplicationUpdateAvailable": "Dostępna aktualizacja aplikacji", + "NotificationOptionApplicationUpdateInstalled": "Zaktualizowano aplikację", + "NotificationOptionAudioPlayback": "Rozpoczęto odtwarzanie muzyki", + "NotificationOptionAudioPlaybackStopped": "Odtwarzanie dźwięku zatrzymane", + "NotificationOptionCameraImageUploaded": "Przekazano obraz z urządzenia przenośnego", + "NotificationOptionInstallationFailed": "Nieudana instalacja", + "NotificationOptionNewLibraryContent": "Dodano nową zawartość", + "NotificationOptionPluginError": "Awaria wtyczki", + "NotificationOptionPluginInstalled": "Zainstalowano wtyczkę", + "NotificationOptionPluginUninstalled": "Odinstalowano wtyczkę", + "NotificationOptionPluginUpdateInstalled": "Zaktualizowano wtyczkę", + "NotificationOptionServerRestartRequired": "Wymagane ponowne uruchomienie serwera", + "NotificationOptionTaskFailed": "Awaria zaplanowanego zadania", + "NotificationOptionUserLockedOut": "Użytkownik zablokowany", + "NotificationOptionVideoPlayback": "Rozpoczęto odtwarzanie wideo", + "NotificationOptionVideoPlaybackStopped": "Zatrzymano odtwarzanie wideo", + "Photos": "Zdjęcia", + "Playlists": "Listy odtwarzania", + "Plugin": "Wtyczka", + "PluginInstalledWithName": "{0} zostało zainstalowane", + "PluginUninstalledWithName": "{0} odinstalowane", + "PluginUpdatedWithName": "{0} zaktualizowane", + "ProviderValue": "Dostawca: {0}", + "ScheduledTaskFailedWithName": "Nieudane {0}", + "ScheduledTaskStartedWithName": "Rozpoczęto {0}", + "ServerNameNeedsToBeRestarted": "{0} wymaga ponownego uruchomienia", + "Shows": "Seriale", + "Songs": "Utwory", + "StartupEmbyServerIsLoading": "Trwa wczytywanie serwera Jellyfin. Spróbuj ponownie za chwilę.", + "SubtitleDownloadFailureFromForItem": "Nieudane pobieranie napisów z {0} dla {1}", + "Sync": "Synchronizacja", + "System": "System", + "TvShows": "Seriale", + "User": "Użytkownik", + "UserCreatedWithName": "Użytkownik {0} został utworzony", + "UserDeletedWithName": "Użytkownik {0} został usunięty", + "UserDownloadingItemWithValues": "{0} pobiera {1}", + "UserLockedOutWithName": "Użytkownik {0} został zablokowany", + "UserOfflineFromDevice": "{0} z {1} został rozłączony", + "UserOnlineFromDevice": "{0} połączył się z {1}", + "UserPasswordChangedWithName": "Hasło użytkownika {0} zostało zmienione", + "UserPolicyUpdatedWithName": "Zmieniono zasady użytkowania dla {0}", + "UserStartedPlayingItemWithValues": "{0} odtwarza {1} na {2}", + "UserStoppedPlayingItemWithValues": "{0} zakończył odtwarzanie {1} na {2}", + "ValueHasBeenAddedToLibrary": "{0} został dodany do biblioteki mediów", + "ValueSpecialEpisodeName": "Specjalne - {0}", + "VersionNumber": "Wersja {0}", + "TaskDownloadMissingSubtitlesDescription": "Przeszukuje internet w poszukiwaniu brakujących napisów w oparciu o konfigurację metadanych.", + "TaskDownloadMissingSubtitles": "Pobierz brakujące napisy", + "TaskRefreshChannelsDescription": "Odświeża informacje o kanałach internetowych.", + "TaskRefreshChannels": "Odśwież kanały", + "TaskCleanTranscodeDescription": "Usuwa transkodowane pliki starsze niż 1 dzień.", + "TaskCleanTranscode": "Wyczyść folder transkodowania", + "TaskUpdatePluginsDescription": "Pobiera i instaluje aktualizacje wtyczek, które są skonfigurowane do automatycznej aktualizacji.", + "TaskUpdatePlugins": "Aktualizuj wtyczki", + "TaskRefreshPeopleDescription": "Odświeża metadane o aktorów i reżyserów w Twojej bibliotece mediów.", + "TaskRefreshPeople": "Odśwież obsadę", + "TaskCleanLogsDescription": "Kasuje pliki logów starsze niż {0} dni.", + "TaskCleanLogs": "Wyczyść folder logów", + "TaskRefreshLibraryDescription": "Skanuje Twoją bibliotekę mediów dla nowych plików i odświeżenia metadanych.", + "TaskRefreshLibrary": "Skanuj bibliotekę mediów", + "TaskRefreshChapterImagesDescription": "Tworzy miniatury dla filmów posiadających rozdziały.", + "TaskRefreshChapterImages": "Wydobądź grafiki rozdziałów", + "TaskCleanCacheDescription": "Usuwa niepotrzebne i przestarzałe pliki cache.", + "TaskCleanCache": "Wyczyść folder Cache", + "TasksChannelsCategory": "Kanały internetowe", + "TasksApplicationCategory": "Aplikacja", + "TasksLibraryCategory": "Biblioteka", + "TasksMaintenanceCategory": "Konserwacja", + "TaskCleanActivityLogDescription": "Usuwa wpisy dziennika aktywności starsze niż skonfigurowany wiek.", + "TaskCleanActivityLog": "Czyść dziennik aktywności", + "Undefined": "Nieustalony", + "Forced": "Wymuszony", + "Default": "Domyślne", + "TaskOptimizeDatabase": "Optymalizuj bazę danych", + "TaskOptimizeDatabaseDescription": "Kompaktuje bazę danych i obcina wolne miejsce. Uruchomienie tego zadania po przeskanowaniu biblioteki lub dokonaniu innych zmian, które pociągają za sobą modyfikacje bazy danych, może poprawić wydajność.", + "External": "Zewnętrzny", + "TaskKeyframeExtractorDescription": "Wyodrębnia klatki kluczowe z plików wideo w celu utworzenia bardziej precyzyjnych list odtwarzania HLS. To zadanie może trwać przez długi czas.", + "TaskKeyframeExtractor": "Ekstraktor klatek kluczowych", + "HearingImpaired": "Niedosłyszący", + "TaskRefreshTrickplayImages": "Generuj obrazy Trickplay", + "TaskRefreshTrickplayImagesDescription": "Tworzy podglądy Trickplay dla filmów we włączonych bibliotekach.", + "TaskCleanCollectionsAndPlaylistsDescription": "Usuwa elementy z kolekcji i list odtwarzania, które już nie istnieją.", + "TaskCleanCollectionsAndPlaylists": "Oczyść kolekcje i listy odtwarzania", + "TaskAudioNormalization": "Normalizacja dźwięku", + "TaskAudioNormalizationDescription": "Skanuje pliki w poszukiwaniu danych normalizacji dźwięku.", + "TaskDownloadMissingLyrics": "Pobierz brakujące słowa", + "TaskDownloadMissingLyricsDescription": "Pobierz słowa piosenek", + "TaskExtractMediaSegments": "Skanowanie segmentów mediów", + "TaskMoveTrickplayImages": "Migruj lokalizację obrazu Trickplay", + "TaskExtractMediaSegmentsDescription": "Wyodrębnia lub pobiera segmenty mediów z wtyczek obsługujących MediaSegment.", + "TaskMoveTrickplayImagesDescription": "Przenosi istniejące pliki Trickplay zgodnie z ustawieniami biblioteki.", + "CleanupUserDataTaskDescription": "Usuwa wszystkie dane użytkownika (stan oglądanych, status ulubionych itp.) z mediów, które nie są dostępne od co najmniej 90 dni.", + "CleanupUserDataTask": "Zadanie czyszczenia danych użytkownika" +} diff --git a/Emby.Server.Implementations/Localization/Core/pr.json b/Emby.Server.Implementations/Localization/Core/pr.json new file mode 100644 index 00000000..fee7e65f --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/pr.json @@ -0,0 +1,122 @@ +{ + "Books": "Scrolls", + "AuthenticationSucceededWithUserName": "{0} passed yer trial", + "Artists": "Artistas", + "Songs": "Shantees", + "Albums": "Tomes", + "Photos": "Paintings", + "NotificationOptionUserLockedOut": "Crewmate sent to the brig", + "HeaderContinueWatching": "Continue Yer Journey", + "Folders": "Chests", + "Application": "Captain", + "DeviceOnlineWithName": "{0} joined yer crew", + "DeviceOfflineWithName": "{0} abandoned ship", + "AppDeviceValues": "Captain: {0}, Ship: {1}", + "CameraImageUploadedFrom": "Yer looking glass has glimpsed another painting from {0}", + "Collections": "Barrels", + "ItemAddedWithName": "{0} is now with yer treasure", + "Default": "Normal-like", + "FailedLoginAttemptWithUserName": "Ye failed to enter from {0}", + "Favorites": "Finest Loot", + "ItemRemovedWithName": "{0} was taken from yer treasure", + "LabelIpAddressValue": "Ship's coordinates: {0}", + "Genres": "types o' booty", + "TaskDownloadMissingSubtitlesDescription": "Scours the seven seas o' the internet for subtitles that be missin' based on the captain's map o' metadata.", + "HeaderAlbumArtists": "Buccaneers o' the musical arts", + "HeaderFavoriteAlbums": "Beloved booty o' musical adventures", + "HeaderFavoriteArtists": "Treasured scallywags o' the creative seas", + "Channels": "Channels", + "Forced": "Pressed", + "External": "Outboard", + "HeaderFavoriteEpisodes": "Treasured Tales", + "HeaderFavoriteShows": "Treasured Tales", + "ChapterNameValue": "Piece {0}", + "HeaderFavoriteSongs": "Treasured Chimes", + "HeaderNextUp": "Incoming", + "HeaderLiveTV": "Scrying Glass", + "HearingImpaired": "Hard o' Hearing", + "LabelRunningTimeValue": "Journey duration: {0}", + "MessageApplicationUpdated": "Yer Map of the Seas has been scribbled", + "HomeVideos": "Yer Onboard Booty", + "MixedContent": "Jumbled loot", + "Music": "Tunes", + "NameInstallFailed": "Ye couldn't bring {0} aboard yer ship", + "MessageApplicationUpdatedTo": "Yer Map of the Seas has been scribbled with {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Yer Map Drawer has been rescribbled to {0}", + "MessageServerConfigurationUpdated": "Yer Map drawer has been rescribbled", + "Inherit": "Carry on what be passed along", + "Latest": "Newfangled", + "Movies": "Moving pictures", + "NewVersionIsAvailable": "A fresh build o’ Jellyfin Server be waitin’ fer ye to fetch.", + "NotificationOptionPluginInstalled": "Plugin nailed down", + "NotificationOptionVideoPlayback": "Video playback be underway", + "ScheduledTaskFailedWithName": "{0} ran aground", + "StartupEmbyServerIsLoading": "Jellyfin Server be preparin’ the ship. Try yer luck again soon.", + "UserOfflineFromDevice": "{0} severed ties with {1}", + "UserDownloadingItemWithValues": "{0} be haulin’ in {1}", + "UserStartedPlayingItemWithValues": "{0} be playin’ {1} aboard {2}", + "ValueHasBeenAddedToLibrary": "{0} be stashed in yer treasure trove", + "TaskCleanCacheDescription": "Wipes away cache cargo no longer called fer.", + "TaskCleanLogsDescription": "Clears the logbook o’ entries older than {0} days.", + "TaskRefreshPeopleDescription": "Refreshes the charts fer actors an’ directors in yer Treasure Trove.", + "UserLockedOutWithName": "Matey {0} be denied boarding", + "TaskAudioNormalization": "Steadyin’ the shanties", + "TaskAudioNormalizationDescription": "Scans files fer shanty steadiyin’ data.", + "HeaderRecordingGroups": "Loggin' Groups", + "MusicVideos": "Shanty films", + "Playlists": "Lists o’ plunder", + "Plugin": "Extra sail", + "NotificationOptionVideoPlaybackStopped": "Video playback dropped anchor", + "NameSeasonNumber": "Saga {0}", + "NameSeasonUnknown": "Saga be Lost", + "NotificationOptionApplicationUpdateAvailable": "A fresh build awaits", + "NotificationOptionApplicationUpdateInstalled": "App upgrade be aboard", + "NotificationOptionAudioPlayback": "Audio playback be rollin", + "NotificationOptionAudioPlaybackStopped": "Audio playback dropped anchor", + "NotificationOptionCameraImageUploaded": "Spyglass shot be hoisted", + "NotificationOptionInstallationFailed": "Install be wrecked", + "NotificationOptionNewLibraryContent": "Fresh plunder ready to claim", + "NotificationOptionPluginError": "Plugin ran aground", + "NotificationOptionPluginUninstalled": "Plugin cast overboard", + "NotificationOptionPluginUpdateInstalled": "Plugin patched ‘n ready", + "NotificationOptionServerRestartRequired": "Server be due fer a restart", + "NotificationOptionTaskFailed": "Set chore went overboard", + "TaskRefreshLibraryDescription": "Searches the Treasure Trove fer new plunder ‘n updates the charts.", + "PluginInstalledWithName": "{0} nailed down", + "TaskCleanLogs": "Swab the Log Hold", + "TaskRefreshPeople": "Freshen the Mateys", + "PluginUninstalledWithName": "{0} sent t’ Davy Jones", + "PluginUpdatedWithName": "{0} patched ‘n ready", + "ProviderValue": "Supplier o’ goods: {0}", + "ScheduledTaskStartedWithName": "{0} set sail", + "ServerNameNeedsToBeRestarted": "{0} be cravin’ a restart", + "Shows": "Sagas", + "SubtitleDownloadFailureFromForItem": "Subtitles be sunk fetchin’ from {0} fer {1}", + "Sync": "Match the tides", + "System": "The ship’s works", + "TvShows": "TV Sagas", + "Undefined": "Uncharted", + "User": "Matey", + "UserCreatedWithName": "Matey {0} joined the crew", + "UserDeletedWithName": "Matey {0} cast overboard", + "UserOnlineFromDevice": "{0} be aboard ship from {1}", + "UserPasswordChangedWithName": "New passphrase set fer Matey {0}", + "UserPolicyUpdatedWithName": "Ship rules be changed fer {0}", + "UserStoppedPlayingItemWithValues": "{0} be done playin’ {1} on {2", + "ValueSpecialEpisodeName": "Special Tale – {0}", + "VersionNumber": "Edition {0}", + "TasksMaintenanceCategory": "Hull patchin’", + "TasksLibraryCategory": "Treasure Trove", + "TasksApplicationCategory": "Ship", + "TaskCleanActivityLog": "Clear the Ship’s Log", + "TaskCleanActivityLogDescription": "Purges ship’s logs older than the chosen time.", + "TaskCleanCache": "Sweep the Cache Chest", + "TaskRefreshChapterImages": "Claim chapter portraits", + "TaskRefreshChapterImagesDescription": "Paints wee portraits fer videos that own chapters.", + "TaskRefreshLibrary": "Scan the Treasure Trove", + "TasksChannelsCategory": "Channels o' thy Internet", + "TaskRefreshTrickplayImages": "Summon the picture tricks", + "TaskRefreshTrickplayImagesDescription": "Summons picture trick previews for videos in ye enabled book roost", + "TaskUpdatePlugins": "Resummon yer Plugins", + "TaskCleanTranscode": "Swab Ye Transcode Directory" +} diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json new file mode 100644 index 00000000..8e76c6c6 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json @@ -0,0 +1,141 @@ +{ + "Albums": "Álbuns", + "AppDeviceValues": "App: {0}, Dispositivo: {1}", + "Application": "Aplicativo", + "Artists": "Artistas", + "AuthenticationSucceededWithUserName": "{0} autenticado com sucesso", + "Books": "Livros", + "CameraImageUploadedFrom": "Uma nova imagem da câmera foi enviada de {0}", + "Channels": "Canais", + "ChapterNameValue": "Capítulo {0}", + "Collections": "Coleções", + "DeviceOfflineWithName": "{0} se desconectou", + "DeviceOnlineWithName": "{0} se conectou", + "FailedLoginAttemptWithUserName": "Falha na tentativa de login de {0}", + "Favorites": "Favoritos", + "Folders": "Pastas", + "Genres": "Gêneros", + "HeaderAlbumArtists": "Artistas do Álbum", + "HeaderContinueWatching": "Continuar assistindo", + "HeaderFavoriteAlbums": "Álbuns Favoritos", + "HeaderFavoriteArtists": "Artistas favoritos", + "HeaderFavoriteEpisodes": "Episódios favoritos", + "HeaderFavoriteShows": "Séries favoritas", + "HeaderFavoriteSongs": "Músicas favoritas", + "HeaderLiveTV": "TV ao Vivo", + "HeaderNextUp": "A Seguir", + "HeaderRecordingGroups": "Grupos de Gravação", + "HomeVideos": "Vídeos caseiros", + "Inherit": "Herdar", + "ItemAddedWithName": "{0} foi adicionado à biblioteca", + "ItemRemovedWithName": "{0} foi removido da biblioteca", + "LabelIpAddressValue": "Endereço IP: {0}", + "LabelRunningTimeValue": "Tempo de execução: {0}", + "Latest": "Recentes", + "MessageApplicationUpdated": "Servidor Jellyfin atualizado", + "MessageApplicationUpdatedTo": "Servidor Jellyfin atualizado para {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "A seção {0} da configuração do servidor foi atualizada", + "MessageServerConfigurationUpdated": "A configuração do servidor foi atualizada", + "MixedContent": "Conteúdo misto", + "Movies": "Filmes", + "Music": "Música", + "MusicVideos": "Videoclipes", + "NameInstallFailed": "A instalação de {0} falhou", + "NameSeasonNumber": "Temporada {0}", + "NameSeasonUnknown": "Temporada Desconhecida", + "NewVersionIsAvailable": "Uma nova versão do Servidor Jellyfin está disponível para download.", + "NotificationOptionApplicationUpdateAvailable": "Atualização do aplicativo disponível", + "NotificationOptionApplicationUpdateInstalled": "Atualização do aplicativo instalada", + "NotificationOptionAudioPlayback": "Reprodução de áudio iniciada", + "NotificationOptionAudioPlaybackStopped": "Reprodução de áudio parada", + "NotificationOptionCameraImageUploaded": "Imagem da câmera enviada", + "NotificationOptionInstallationFailed": "Falha na instalação", + "NotificationOptionNewLibraryContent": "Novo conteúdo adicionado", + "NotificationOptionPluginError": "Falha de plugin", + "NotificationOptionPluginInstalled": "Plugin instalado", + "NotificationOptionPluginUninstalled": "Plugin desinstalado", + "NotificationOptionPluginUpdateInstalled": "Atualização de plugin instalada", + "NotificationOptionServerRestartRequired": "Necessário reiniciar servidor", + "NotificationOptionTaskFailed": "Falha na tarefa agendada", + "NotificationOptionUserLockedOut": "Usuário bloqueado", + "NotificationOptionVideoPlayback": "Reprodução de vídeo iniciada", + "NotificationOptionVideoPlaybackStopped": "Reprodução de vídeo parada", + "Photos": "Fotos", + "Playlists": "Listas de Reprodução", + "Plugin": "Plugin", + "PluginInstalledWithName": "{0} foi instalado", + "PluginUninstalledWithName": "{0} foi desinstalado", + "PluginUpdatedWithName": "{0} foi atualizado", + "ProviderValue": "Provedor: {0}", + "ScheduledTaskFailedWithName": "{0} falhou", + "ScheduledTaskStartedWithName": "{0} iniciada", + "ServerNameNeedsToBeRestarted": "O servidor {0} precisa ser reiniciado", + "Shows": "Séries", + "Songs": "Músicas", + "StartupEmbyServerIsLoading": "O Servidor Jellyfin está carregando. Por favor, tente novamente mais tarde.", + "SubtitleDownloadFailureFromForItem": "Houve um problema ao baixar as legendas de {0} para {1}", + "Sync": "Sincronizar", + "System": "Sistema", + "TvShows": "Séries", + "User": "Usuário", + "UserCreatedWithName": "O usuário {0} foi criado", + "UserDeletedWithName": "O usuário {0} foi excluído", + "UserDownloadingItemWithValues": "{0} está baixando {1}", + "UserLockedOutWithName": "Usuário {0} foi bloqueado", + "UserOfflineFromDevice": "{0} se desconectou de {1}", + "UserOnlineFromDevice": "{0} está online em {1}", + "UserPasswordChangedWithName": "A senha foi alterada para o usuário {0}", + "UserPolicyUpdatedWithName": "A política de usuário foi atualizada para {0}", + "UserStartedPlayingItemWithValues": "{0} está reproduzindo {1} em {2}", + "UserStoppedPlayingItemWithValues": "{0} parou de reproduzir {1} em {2}", + "ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua biblioteca de mídia", + "ValueSpecialEpisodeName": "Especial - {0}", + "VersionNumber": "Versão {0}", + "TaskDownloadMissingSubtitlesDescription": "Procurar na internet por legendas faltando baseado na configuração de metadados.", + "TaskDownloadMissingSubtitles": "Baixar legendas que estão faltando", + "TaskRefreshChannelsDescription": "Atualizar informação de canais da internet .", + "TaskRefreshChannels": "Atualizar Canais", + "TaskCleanTranscodeDescription": "Deletar arquivos de transcodificação com mais de um dia de criação.", + "TaskCleanTranscode": "Limpar pasta de transcodificação", + "TaskUpdatePluginsDescription": "Baixa e instala atualizações para plugins que estão configurados para atualizar automaticamente.", + "TaskUpdatePlugins": "Atualizar Plugins", + "TaskRefreshPeopleDescription": "Atualiza metadados para atores e diretores na sua biblioteca de mídia.", + "TaskRefreshPeople": "Atualizar pessoas", + "TaskCleanLogsDescription": "Deletar arquivos temporários com mais de {0} dias.", + "TaskCleanLogs": "Limpar pasta de logs", + "TaskRefreshLibraryDescription": "Escaneie a sua biblioteca de mídia para arquivos novos e atualize os metadados.", + "TaskRefreshLibrary": "Escanear a Biblioteca de Mídia", + "TaskRefreshChapterImagesDescription": "Criar miniaturas para vídeos que tem capítulos.", + "TaskRefreshChapterImages": "Extrair imagens dos capítulos", + "TaskCleanCacheDescription": "Deletar arquivos temporários que não são mais necessários para o sistema.", + "TaskCleanCache": "Limpar Arquivos Temporários", + "TasksChannelsCategory": "Canais da Internet", + "TasksApplicationCategory": "Aplicação", + "TasksLibraryCategory": "Biblioteca", + "TasksMaintenanceCategory": "Manutenção", + "TaskCleanActivityLogDescription": "Apaga o registro de atividades mais antigo que a idade configurada.", + "TaskCleanActivityLog": "Limpar Registro de Atividades", + "Undefined": "Indefinido", + "Forced": "Forçado", + "Default": "Padrão", + "TaskOptimizeDatabaseDescription": "Compactar base de dados e liberar espaço livre. Executar esta tarefa após realizar mudanças que impliquem em modificações da base de dados pode trazer melhorias de desempenho.", + "TaskOptimizeDatabase": "Otimizar base de dados", + "TaskKeyframeExtractor": "Extrator de quadro-chave", + "TaskKeyframeExtractorDescription": "Extrai quadros-chave de arquivos de vídeo para criar listas de reprodução HLS mais precisas. Esta tarefa pode ser executada por um longo tempo.", + "External": "Externo", + "HearingImpaired": "Deficiência Auditiva", + "TaskRefreshTrickplayImages": "Gerar imagens Trickplay", + "TaskRefreshTrickplayImagesDescription": "Cria prévias Trickplay para vídeos em bibliotecas em que o recurso está habilitado.", + "TaskCleanCollectionsAndPlaylists": "Limpe coleções e playlists", + "TaskCleanCollectionsAndPlaylistsDescription": "Remove itens de coleções e playlists que não existem mais.", + "TaskAudioNormalization": "Normalização de áudio", + "TaskAudioNormalizationDescription": "Examina os ficheiros em busca de dados de normalização de áudio.", + "TaskDownloadMissingLyricsDescription": "Baixar letras para músicas", + "TaskDownloadMissingLyrics": "Baixar letra faltante", + "TaskMoveTrickplayImagesDescription": "Move os arquivos do trickplay de acordo com as configurações da biblioteca.", + "TaskExtractMediaSegments": "Varredura do segmento de mídia", + "TaskExtractMediaSegmentsDescription": "Extrai ou obtém segmentos de mídia de plug-ins habilitados para MediaSegment.", + "TaskMoveTrickplayImages": "Migrar o local da imagem do Trickplay", + "CleanupUserDataTask": "Tarefa de limpeza de dados do usuário", + "CleanupUserDataTaskDescription": "Limpa todos os dados do usuário (estado de visualização, status de favorito, etc.) de mídias que não estão presentes por pelo menos 90 dias." +} diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json new file mode 100644 index 00000000..c2ce2ba4 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json @@ -0,0 +1,141 @@ +{ + "Albums": "Álbuns", + "AppDeviceValues": "Aplicação: {0}, Dispositivo: {1}", + "Application": "Aplicação", + "Artists": "Artistas", + "AuthenticationSucceededWithUserName": "{0} autenticado com sucesso", + "Books": "Livros", + "CameraImageUploadedFrom": "Uma nova imagem da câmara foi enviada a partir de {0}", + "Channels": "Canais", + "ChapterNameValue": "Capítulo {0}", + "Collections": "Coleções", + "DeviceOfflineWithName": "{0} desligou-se", + "DeviceOnlineWithName": "{0} ligou-se", + "FailedLoginAttemptWithUserName": "Tentativa de login falhada a partir de {0}", + "Favorites": "Favoritos", + "Folders": "Pastas", + "Genres": "Géneros", + "HeaderAlbumArtists": "Artistas do álbum", + "HeaderContinueWatching": "Continuar a ver", + "HeaderFavoriteAlbums": "Álbuns Favoritos", + "HeaderFavoriteArtists": "Artistas Favoritos", + "HeaderFavoriteEpisodes": "Episódios Favoritos", + "HeaderFavoriteShows": "Séries Favoritas", + "HeaderFavoriteSongs": "Músicas Favoritas", + "HeaderLiveTV": "TV em Direto", + "HeaderNextUp": "A Seguir", + "HeaderRecordingGroups": "Grupos de Gravação", + "HomeVideos": "Vídeos Caseiros", + "Inherit": "Herdar", + "ItemAddedWithName": "{0} foi adicionado à mediateca", + "ItemRemovedWithName": "{0} foi removido da mediateca", + "LabelIpAddressValue": "Endereço IP: {0}", + "LabelRunningTimeValue": "Duração: {0}", + "Latest": "Mais Recente", + "MessageApplicationUpdated": "O servidor Jellyfin foi atualizado", + "MessageApplicationUpdatedTo": "O servidor Jellyfin foi atualizado para a versão {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Configurações de servidor na secção {0} foram atualizadas", + "MessageServerConfigurationUpdated": "A configuração do servidor foi atualizada", + "MixedContent": "Conteúdo Misto", + "Movies": "Filmes", + "Music": "Música", + "MusicVideos": "Videoclipes", + "NameInstallFailed": "{0} falha na instalação", + "NameSeasonNumber": "Temporada {0}", + "NameSeasonUnknown": "Temporada Desconhecida", + "NewVersionIsAvailable": "Está disponível para transferência uma nova versão do servidor Jellyfin.", + "NotificationOptionApplicationUpdateAvailable": "Atualização de aplicação disponível", + "NotificationOptionApplicationUpdateInstalled": "Atualização de aplicação instalada", + "NotificationOptionAudioPlayback": "Reprodução Iniciada", + "NotificationOptionAudioPlaybackStopped": "Reprodução Parada", + "NotificationOptionCameraImageUploaded": "Imagem da câmara enviada", + "NotificationOptionInstallationFailed": "Falha na instalação", + "NotificationOptionNewLibraryContent": "Novo conteúdo adicionado", + "NotificationOptionPluginError": "Falha na extensão", + "NotificationOptionPluginInstalled": "Extensão instalada", + "NotificationOptionPluginUninstalled": "Extensão desinstalada", + "NotificationOptionPluginUpdateInstalled": "Extensão atualizada", + "NotificationOptionServerRestartRequired": "Necessário reiniciar o servidor", + "NotificationOptionTaskFailed": "Falha em tarefa agendada", + "NotificationOptionUserLockedOut": "Utilizador bloqueado", + "NotificationOptionVideoPlayback": "Reprodução do vídeo iniciada", + "NotificationOptionVideoPlaybackStopped": "Reprodução do vídeo parada", + "Photos": "Fotografias", + "Playlists": "Playlists", + "Plugin": "Extensão", + "PluginInstalledWithName": "{0} foi instalado", + "PluginUninstalledWithName": "{0} foi desinstalado", + "PluginUpdatedWithName": "{0} foi atualizado", + "ProviderValue": "Provider: {0}", + "ScheduledTaskFailedWithName": "{0} falhou", + "ScheduledTaskStartedWithName": "{0} iniciou", + "ServerNameNeedsToBeRestarted": "{0} necessita de ser reiniciado", + "Shows": "Séries", + "Songs": "Músicas", + "StartupEmbyServerIsLoading": "O servidor Jellyfin está a iniciar. Tente novamente mais tarde.", + "SubtitleDownloadFailureFromForItem": "Falha na transferência de legendas a partir de {0} para {1}", + "Sync": "Sincronização", + "System": "Sistema", + "TvShows": "Séries", + "User": "Utilizador", + "UserCreatedWithName": "Utilizador {0} criado", + "UserDeletedWithName": "Utilizador {0} apagado", + "UserDownloadingItemWithValues": "{0} está a transferir {1}", + "UserLockedOutWithName": "Utilizador {0} bloqueado", + "UserOfflineFromDevice": "{0} desligou-se a partir de {1}", + "UserOnlineFromDevice": "{0} ligou-se a partir de {1}", + "UserPasswordChangedWithName": "Palavra-passe alterada para o utilizador {0}", + "UserPolicyUpdatedWithName": "Política de utilizador alterada para {0}", + "UserStartedPlayingItemWithValues": "{0} está a reproduzir {1} em {2}", + "UserStoppedPlayingItemWithValues": "{0} terminou a reprodução de {1} em {2}", + "ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua mediateca", + "ValueSpecialEpisodeName": "Especial - {0}", + "VersionNumber": "Versão {0}", + "TaskDownloadMissingSubtitlesDescription": "Procurar na internet por legendas em falta baseado na configuração de metadados.", + "TaskDownloadMissingSubtitles": "Transferir legendas em falta", + "TaskRefreshChannelsDescription": "Atualizar informação sobre canais da Internet.", + "TaskRefreshChannels": "Atualizar Canais", + "TaskCleanTranscodeDescription": "Apagar ficheiros de transcode com mais de um dia.", + "TaskCleanTranscode": "Limpar a Diretoria de Transcode", + "TaskUpdatePluginsDescription": "Faz o download e instala updates para os plugins que estão configurados para atualizar automaticamente.", + "TaskUpdatePlugins": "Atualizar Plugins", + "TaskRefreshPeopleDescription": "Atualizar metadados para elenco e equipa técnica da tua mediateca.", + "TaskRefreshPeople": "Atualizar Pessoas", + "TaskCleanLogsDescription": "Apagar ficheiros de log que têm mais de {0} dias.", + "TaskCleanLogs": "Limpar a Diretoria de Logs", + "TaskRefreshLibraryDescription": "Analisar a mediateca para novos ficheiros e atualizar os metadados.", + "TaskRefreshLibrary": "Analisar mediateca", + "TaskRefreshChapterImagesDescription": "Criar thumbnails para os vídeos que têm capítulos.", + "TaskRefreshChapterImages": "Extrair Imagens dos Capítulos", + "TaskCleanCacheDescription": "Apagar ficheiros em cache que já não são necessários.", + "TaskCleanCache": "Limpar Cache", + "TasksChannelsCategory": "Canais da Internet", + "TasksApplicationCategory": "Aplicação", + "TasksLibraryCategory": "Mediateca", + "TasksMaintenanceCategory": "Manutenção", + "TaskCleanActivityLogDescription": "Apaga as entradas do registo de atividade anteriores à data configurada.", + "TaskCleanActivityLog": "Limpar registo de atividade", + "Undefined": "Indefinido", + "Forced": "Forçado", + "Default": "Predefinição", + "TaskOptimizeDatabaseDescription": "Otimiza e liberta espaço livre na base de dados. A execução desta tarefa depois de analisar a mediateca ou efetuar outras alterações que impliquem modificações na base de dados pode melhorar o desempenho.", + "TaskOptimizeDatabase": "Otimizar base de dados", + "TaskKeyframeExtractorDescription": "Extrai quadros-chave de ficheiros de video para criar listas de reprodução HLS mais precisas. Esta tarefa pode demorar algum tempo.", + "TaskKeyframeExtractor": "Extrator de Quadros-chave", + "External": "Externo", + "HearingImpaired": "Surdo", + "TaskRefreshTrickplayImages": "Gerar imagens de trickplay", + "TaskRefreshTrickplayImagesDescription": "Cria pré-visualizações de trickplay para vídeos nas bibliotecas ativadas.", + "TaskCleanCollectionsAndPlaylistsDescription": "Remove itens de coleções e listas de reprodução que já não existem.", + "TaskCleanCollectionsAndPlaylists": "Limpar coleções e listas de reprodução", + "TaskAudioNormalizationDescription": "Analisa os ficheiros para obter dados de normalização de áudio.", + "TaskAudioNormalization": "Normalização de áudio", + "TaskExtractMediaSegments": "Analisar segmentos de multimédia", + "TaskDownloadMissingLyrics": "Transferir letra em falta", + "TaskMoveTrickplayImages": "Migrar a localização da imagem do Trickplay", + "TaskDownloadMissingLyricsDescription": "Transferir letra para músicas", + "TaskExtractMediaSegmentsDescription": "Extrai ou obtém segmentos de multimédia a partir de plugins com suporte para MediaSegment.", + "TaskMoveTrickplayImagesDescription": "Move os ficheiros trickplay existentes de acordo com as definições da mediateca.", + "CleanupUserDataTaskDescription": "Apaga todos os dados de utilizador (estados de reprodução, favoritos, etc) de arquivos média não presentes há 90 dias ou mais.", + "CleanupUserDataTask": "Limpeza de dados de utilizador" +} diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json new file mode 100644 index 00000000..9ae346e2 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/pt.json @@ -0,0 +1,141 @@ +{ + "HeaderLiveTV": "TV Em Direto", + "Collections": "Coleções", + "Books": "Livros", + "Artists": "Artistas", + "Albums": "Álbuns", + "HeaderNextUp": "A Seguir", + "HeaderFavoriteSongs": "Músicas Favoritas", + "HeaderFavoriteArtists": "Artistas Favoritos", + "HeaderFavoriteAlbums": "Álbuns Favoritos", + "HeaderFavoriteEpisodes": "Episódios Favoritos", + "HeaderFavoriteShows": "Séries Favoritas", + "HeaderContinueWatching": "Continuar a ver", + "HeaderAlbumArtists": "Artistas do Álbum", + "Genres": "Géneros", + "Folders": "Pastas", + "Favorites": "Favoritos", + "Channels": "Canais", + "UserDownloadingItemWithValues": "{0} está sendo baixado {1}", + "VersionNumber": "Versão {0}", + "ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua mediateca", + "UserStoppedPlayingItemWithValues": "{0} terminou a reprodução de {1} em {2}", + "UserStartedPlayingItemWithValues": "{0} está reproduzindo {1} em {2}", + "UserPolicyUpdatedWithName": "A política do usuário {0} foi alterada", + "UserPasswordChangedWithName": "A senha do usuário {0} foi alterada", + "UserOnlineFromDevice": "{0} está online a partir de {1}", + "UserOfflineFromDevice": "{0} desconectou-se a partir de {1}", + "UserLockedOutWithName": "O usuário {0} foi bloqueado", + "UserDeletedWithName": "O usuário {0} foi removido", + "UserCreatedWithName": "O usuário {0} foi criado", + "User": "Usuário", + "TvShows": "Séries", + "System": "Sistema", + "SubtitleDownloadFailureFromForItem": "Falha na transferência de legendas de {0} para {1}", + "StartupEmbyServerIsLoading": "O servidor Jellyfin está iniciando. Tente novamente dentro de momentos.", + "ServerNameNeedsToBeRestarted": "{0} necessita ser reiniciado", + "ScheduledTaskStartedWithName": "{0} iniciou", + "ScheduledTaskFailedWithName": "{0} falhou", + "ProviderValue": "Fornecedor: {0}", + "PluginUpdatedWithName": "{0} foi atualizado", + "PluginUninstalledWithName": "{0} foi desinstalado", + "PluginInstalledWithName": "{0} foi instalado", + "Plugin": "Plugin", + "NotificationOptionVideoPlaybackStopped": "Reprodução de vídeo parada", + "NotificationOptionVideoPlayback": "Reprodução de vídeo iniciada", + "NotificationOptionUserLockedOut": "Usuário bloqueado", + "NotificationOptionTaskFailed": "Falha na tarefa agendada", + "NotificationOptionServerRestartRequired": "É necessário reiniciar o servidor", + "NotificationOptionPluginUpdateInstalled": "Plugin atualizado", + "NotificationOptionPluginUninstalled": "Plugin desinstalado", + "NotificationOptionPluginInstalled": "Plugin instalado", + "NotificationOptionPluginError": "Falha no plugin", + "NotificationOptionNewLibraryContent": "Novo conteúdo adicionado", + "NotificationOptionInstallationFailed": "Falha de instalação", + "NotificationOptionCameraImageUploaded": "Imagem de câmera enviada", + "NotificationOptionAudioPlaybackStopped": "Reprodução Parada", + "NotificationOptionAudioPlayback": "Reprodução Iniciada", + "NotificationOptionApplicationUpdateInstalled": "A atualização do aplicativo foi instalada", + "NotificationOptionApplicationUpdateAvailable": "Uma atualização do aplicativo está disponível", + "NewVersionIsAvailable": "Uma nova versão do servidor Jellyfin está disponível para download.", + "NameSeasonUnknown": "Temporada Desconhecida", + "NameSeasonNumber": "Temporada {0}", + "NameInstallFailed": "Falha na instalação de {0}", + "MusicVideos": "Videoclipes", + "Music": "Música", + "MixedContent": "Conteúdo diverso", + "MessageServerConfigurationUpdated": "A configuração do servidor foi atualizada", + "MessageNamedServerConfigurationUpdatedWithValue": "As configurações do servidor na seção {0} foram atualizadas", + "MessageApplicationUpdatedTo": "O servidor Jellyfin foi atualizado para a versão {0}", + "MessageApplicationUpdated": "O servidor Jellyfin foi atualizado", + "Latest": "Mais Recente", + "LabelRunningTimeValue": "Duração: {0}", + "LabelIpAddressValue": "Endereço de IP: {0}", + "ItemRemovedWithName": "{0} foi removido da mediateca", + "ItemAddedWithName": "{0} foi adicionado à mediateca", + "Inherit": "Herdar", + "HomeVideos": "Vídeos Caseiros", + "HeaderRecordingGroups": "Grupos de Gravação", + "ValueSpecialEpisodeName": "Especial - {0}", + "Sync": "Sincronização", + "Songs": "Músicas", + "Shows": "Séries", + "Playlists": "Playlists", + "Photos": "Fotografias", + "Movies": "Filmes", + "FailedLoginAttemptWithUserName": "Tentativa de início de sessão falhada a partir de {0}", + "DeviceOnlineWithName": "{0} está ligado", + "DeviceOfflineWithName": "{0} desligou-se", + "ChapterNameValue": "Capítulo {0}", + "CameraImageUploadedFrom": "Uma nova imagem da câmara foi enviada a partir de {0}", + "AuthenticationSucceededWithUserName": "{0} autenticado com sucesso", + "Application": "Aplicação", + "AppDeviceValues": "Aplicação: {0}, Dispositivo: {1}", + "TaskCleanCache": "Limpar Diretório de Cache", + "TasksApplicationCategory": "Aplicação", + "TasksLibraryCategory": "Mediateca", + "TasksMaintenanceCategory": "Manutenção", + "TaskRefreshChannels": "Atualizar Canais", + "TaskUpdatePlugins": "Atualizar Plugins", + "TaskCleanLogsDescription": "Deletar arquivos de log que existe a mais de {0} dias.", + "TaskCleanLogs": "Limpar diretório de logs", + "TaskRefreshLibrary": "Analisar mediateca", + "TaskRefreshChapterImagesDescription": "Cria miniaturas para vídeos que têm capítulos.", + "TaskCleanCacheDescription": "Apaga ficheiros em cache que já não são usados pelo sistema.", + "TasksChannelsCategory": "Canais de Internet", + "TaskRefreshChapterImages": "Extrair Imagens do Capítulo", + "TaskDownloadMissingSubtitlesDescription": "Pesquisa na Internet as legendas em falta com base na configuração de metadados.", + "TaskDownloadMissingSubtitles": "Transferir legendas em falta", + "TaskRefreshChannelsDescription": "Atualiza as informações do canal da Internet.", + "TaskCleanTranscodeDescription": "Apagar os ficheiros com mais de um dia, de Transcode.", + "TaskCleanTranscode": "Limpar o diretório de Transcode", + "TaskUpdatePluginsDescription": "Baixa e instala as atualizações para plug-ins configurados para atualização automática.", + "TaskRefreshPeopleDescription": "Atualizar metadados para elenco e equipa técnica da tua mediateca.", + "TaskRefreshPeople": "Atualizar pessoas", + "TaskRefreshLibraryDescription": "Analisar a mediateca para novos ficheiros e atualizar os metadados.", + "TaskCleanActivityLog": "Limpar registro de atividade", + "Undefined": "Indefinido", + "Forced": "Forçado", + "Default": "Predefinição", + "TaskCleanActivityLogDescription": "Apaga itens no registro com idade acima do que é configurado.", + "TaskOptimizeDatabase": "Otimizar base de dados", + "TaskOptimizeDatabaseDescription": "Otimiza e liberta espaço livre na base de dados. A execução desta tarefa depois de analisar a mediateca ou efetuar outras alterações que impliquem modificações na base de dados pode melhorar o desempenho.", + "External": "Externo", + "HearingImpaired": "Problemas auditivos", + "TaskKeyframeExtractor": "Extrator de quadro-chave", + "TaskKeyframeExtractorDescription": "Retira frames chave do video para criar listas HLS precisas. Esta tarefa pode correr durante algum tempo.", + "TaskRefreshTrickplayImages": "Gerar imagens de trickplay", + "TaskRefreshTrickplayImagesDescription": "Cria pré-visualizações de trickplay para vídeos nas bibliotecas ativadas.", + "TaskCleanCollectionsAndPlaylistsDescription": "Remove itens de coleções e listas de reprodução que já não existem.", + "TaskCleanCollectionsAndPlaylists": "Limpar coleções e listas de reprodução", + "TaskAudioNormalizationDescription": "Analisa os ficheiros para obter dados de normalização de áudio.", + "TaskAudioNormalization": "Normalização de áudio", + "TaskDownloadMissingLyrics": "Transferir letra em falta", + "TaskDownloadMissingLyricsDescription": "Transferir letra para músicas", + "TaskMoveTrickplayImagesDescription": "Move os ficheiros trickplay existentes de acordo com as definições da mediateca.", + "TaskExtractMediaSegments": "Analisar segmentos de multimédia", + "TaskExtractMediaSegmentsDescription": "Extrai ou obtém segmentos de multimédia a partir de plugins com suporte para MediaSegment.", + "TaskMoveTrickplayImages": "Migrar a localização da imagem do Trickplay", + "CleanupUserDataTask": "Task de limpeza de dados do usuário", + "CleanupUserDataTaskDescription": "Remove todos os dados do usuário (progresso, favoritos etc) de mídias que não estão presentes há pelo menos 90 dias." +} diff --git a/Emby.Server.Implementations/Localization/Core/ro.json b/Emby.Server.Implementations/Localization/Core/ro.json new file mode 100644 index 00000000..bf71c5af --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ro.json @@ -0,0 +1,141 @@ +{ + "HeaderNextUp": "Urmează", + "VersionNumber": "Versiunea {0}", + "ValueSpecialEpisodeName": "Special - {0}", + "ValueHasBeenAddedToLibrary": "{0} a fost adăugat la biblioteca multimedia", + "UserStoppedPlayingItemWithValues": "{0} a terminat rularea {1} pe {2}", + "UserStartedPlayingItemWithValues": "{0} ruleaza {1} pe {2}", + "UserPolicyUpdatedWithName": "Politica utilizatorului {0} a fost actualizată", + "UserPasswordChangedWithName": "Parola utilizatorului {0} a fost schimbată", + "UserOnlineFromDevice": "{0} este conectat de la {1}", + "UserOfflineFromDevice": "{0} s-a deconectat de la {1}", + "UserLockedOutWithName": "Utilizatorul {0} a fost blocat", + "UserDownloadingItemWithValues": "{0} descarcă {1}", + "UserDeletedWithName": "Utilizatorul {0} a fost șters", + "UserCreatedWithName": "Utilizatorul {0} a fost creat", + "User": "Utilizator", + "TvShows": "Seriale TV", + "System": "Sistem", + "Sync": "Sincronizare", + "SubtitleDownloadFailureFromForItem": "Subtitrările nu au putut fi descărcate de la {0} pentru {1}", + "StartupEmbyServerIsLoading": "Se încarcă serverul Jellyfin. Încercați din nou în scurt timp.", + "Songs": "Melodii", + "Shows": "Seriale", + "ServerNameNeedsToBeRestarted": "{0} trebuie să fie repornit", + "ScheduledTaskStartedWithName": "{0} pornit/ă", + "ScheduledTaskFailedWithName": "{0} eșuat/ă", + "ProviderValue": "Furnizor: {0}", + "PluginUpdatedWithName": "{0} a fost actualizat/ă", + "PluginUninstalledWithName": "{0} a fost dezinstalat", + "PluginInstalledWithName": "{0} a fost instalat", + "Plugin": "Extensie", + "Playlists": "Liste de redare", + "Photos": "Fotografii", + "NotificationOptionVideoPlaybackStopped": "Redarea video oprită", + "NotificationOptionVideoPlayback": "Redare video începută", + "NotificationOptionUserLockedOut": "Utilizatorul a fost blocat", + "NotificationOptionTaskFailed": "Activitate programată eșuată", + "NotificationOptionServerRestartRequired": "Este necesară repornirea serverului", + "NotificationOptionPluginUpdateInstalled": "Actualizarea extensiei este instalată", + "NotificationOptionPluginUninstalled": "Extensie dezinstalată", + "NotificationOptionPluginInstalled": "Extensie instalată", + "NotificationOptionPluginError": "Eroare de extensie", + "NotificationOptionNewLibraryContent": "A fost adăugat conținut nou", + "NotificationOptionInstallationFailed": "Instalare eșuată", + "NotificationOptionCameraImageUploaded": "Imagine încarcată", + "NotificationOptionAudioPlaybackStopped": "Redare audio oprită", + "NotificationOptionAudioPlayback": "A început redarea audio", + "NotificationOptionApplicationUpdateInstalled": "Actualizarea aplicației a fost instalată", + "NotificationOptionApplicationUpdateAvailable": "Este disponibilă o actualizare a aplicației", + "NewVersionIsAvailable": "O nouă versiune a Jellyfin Server este disponibilă pentru descărcare.", + "NameSeasonUnknown": "Sezon Necunoscut", + "NameSeasonNumber": "Sezonul {0}", + "NameInstallFailed": "{0} instalare eșuată", + "MusicVideos": "Videoclipuri muzicale", + "Music": "Muzică", + "Movies": "Filme", + "MixedContent": "Conținut amestecat", + "MessageServerConfigurationUpdated": "Configurarea serverului a fost actualizată", + "MessageNamedServerConfigurationUpdatedWithValue": "Secțiunea de configurare a serverului {0} a fost acualizata", + "MessageApplicationUpdatedTo": "Jellyfin Server a fost actualizat la {0}", + "MessageApplicationUpdated": "Jellyfin Server a fost actualizat", + "Latest": "Cele mai recente", + "LabelRunningTimeValue": "Durată: {0}", + "LabelIpAddressValue": "Adresa IP: {0}", + "ItemRemovedWithName": "{0} a fost eliminat din bibliotecă", + "ItemAddedWithName": "{0} a fost adăugat în bibliotecă", + "Inherit": "Moștenit", + "HomeVideos": "Filme personale", + "HeaderRecordingGroups": "Grupuri de înregistrare", + "HeaderLiveTV": "TV în Direct", + "HeaderFavoriteSongs": "Melodii Favorite", + "HeaderFavoriteShows": "Seriale TV Favorite", + "HeaderFavoriteEpisodes": "Episoade Favorite", + "HeaderFavoriteArtists": "Artiști Favoriți", + "HeaderFavoriteAlbums": "Albume Favorite", + "HeaderContinueWatching": "Vizionează în continuare", + "HeaderAlbumArtists": "Artiști album", + "Genres": "Genuri", + "Folders": "Dosare", + "Favorites": "Preferate", + "FailedLoginAttemptWithUserName": "Încercare de conectare eșuată pentru {0}", + "DeviceOnlineWithName": "{0} este conectat", + "DeviceOfflineWithName": "{0} s-a deconectat", + "Collections": "Colecții", + "ChapterNameValue": "Capitolul {0}", + "Channels": "Canale", + "CameraImageUploadedFrom": "O nouă fotografie a fost încărcată din {0}", + "Books": "Cărți", + "AuthenticationSucceededWithUserName": "{0} autentificare reușită", + "Artists": "Artiști", + "Application": "Aplicație", + "AppDeviceValues": "Aplicație: {0}, Dispozitiv: {1}", + "Albums": "Albume", + "TaskDownloadMissingSubtitlesDescription": "Caută pe internet subtitrările lipsă pe baza configurației metadatelor.", + "TaskDownloadMissingSubtitles": "Descarcă subtitrările lipsă", + "TaskRefreshChannelsDescription": "Actualizează informațiile despre canalul de internet.", + "TaskRefreshChannels": "Actualizează canale", + "TaskCleanTranscodeDescription": "Șterge fișierele de transcodare mai vechi de o zi.", + "TaskCleanTranscode": "Curățați directorul de transcodare", + "TaskUpdatePluginsDescription": "Descarcă și instalează actualizări pentru extensiile care sunt configurate să se actualizeze automat.", + "TaskUpdatePlugins": "Actualizați Extensiile", + "TaskRefreshPeopleDescription": "Actualizează metadatele pentru actori și regizori din biblioteca media.", + "TaskRefreshPeople": "Actualizează Persoanele", + "TaskCleanLogsDescription": "Șterge fișierele jurnal care au mai mult de {0} zile.", + "TaskCleanLogs": "Curățare director jurnal", + "TaskRefreshLibraryDescription": "Scanează biblioteca media pentru fișiere noi și reîmprospătează metadatele.", + "TaskRefreshLibrary": "Scanează Biblioteca Media", + "TaskRefreshChapterImagesDescription": "Creează miniaturi pentru videourile care au capitole.", + "TaskRefreshChapterImages": "Extrage Imaginile de Capitol", + "TaskCleanCacheDescription": "Șterge fișierele cache care nu mai sunt necesare sistemului.", + "TaskCleanCache": "Curățați directorul cache", + "TasksChannelsCategory": "Canale de pe Internet", + "TasksApplicationCategory": "Aplicație", + "TasksLibraryCategory": "Librărie", + "TasksMaintenanceCategory": "Mentenanță", + "TaskCleanActivityLogDescription": "Șterge intrările din jurnalul de activitate mai vechi de data configurată.", + "TaskCleanActivityLog": "Curăță Jurnalul de Activități", + "Undefined": "Nedefinit", + "Forced": "Forțat", + "Default": "Implicit", + "TaskOptimizeDatabaseDescription": "Comprimă baza de date și trunchiază spațiul liber. Rularea acestei sarcini după scanarea bibliotecii sau după efectuarea altor modificări care implică modificări ale bazei de date poate îmbunătăți performanța.", + "TaskOptimizeDatabase": "Optimizează baza de date", + "TaskKeyframeExtractorDescription": "Extrage cadrele cheie din fișierele video pentru a crea liste de redare HLS mai precise. Această sarcină poate rula o perioadă lungă de timp.", + "External": "Extern", + "TaskKeyframeExtractor": "Extractor de cadre cheie", + "HearingImpaired": "Ascultare Impară", + "TaskRefreshTrickplayImages": "Generează imagini Trickplay", + "TaskRefreshTrickplayImagesDescription": "Generează previzualizările trickplay pentru videourile din librăriile selectate.", + "TaskAudioNormalizationDescription": "Scanează fișiere pentru date necesare normalizării sunetului.", + "TaskAudioNormalization": "Normalizare sunet", + "TaskCleanCollectionsAndPlaylists": "Curăță colecțiile și listele de redare", + "TaskCleanCollectionsAndPlaylistsDescription": "Elimină elementele care nu mai există din colecții și liste de redare.", + "TaskExtractMediaSegments": "Scanează segmentele media", + "TaskMoveTrickplayImagesDescription": "Mută fișierele trickplay existente conform setărilor librăriei.", + "TaskExtractMediaSegmentsDescription": "Extrage sau obține segmentele media de la pluginurile MediaSegment activate.", + "TaskMoveTrickplayImages": "Migrează locația imaginii Trickplay", + "TaskDownloadMissingLyrics": "Descarcă versurile lipsă", + "TaskDownloadMissingLyricsDescription": "Descarcă versuri pentru melodii", + "CleanupUserDataTask": "Sarcina de curatare a datelor utilizatorului", + "CleanupUserDataTaskDescription": "Sterge toate datele utilizatorului (starea vizionarii, starea favoritelor etc.) de pe suporturile media care nu mai sunt prezente timp de cel puțin 90 de zile." +} diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json new file mode 100644 index 00000000..03bce0eb --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ru.json @@ -0,0 +1,141 @@ +{ + "Albums": "Альбомы", + "AppDeviceValues": "Приложение: {0}, Устройство: {1}", + "Application": "Приложение", + "Artists": "Исполнители", + "AuthenticationSucceededWithUserName": "{0} - авторизация успешна", + "Books": "Книги", + "CameraImageUploadedFrom": "Новое фото загружено с камеры {0}", + "Channels": "Каналы", + "ChapterNameValue": "Сцена {0}", + "Collections": "Коллекции", + "DeviceOfflineWithName": "{0} - отключено", + "DeviceOnlineWithName": "{0} - подключено", + "FailedLoginAttemptWithUserName": "Неудачная попытка входа с {0}", + "Favorites": "Избранное", + "Folders": "Папки", + "Genres": "Жанры", + "HeaderAlbumArtists": "Исполнители альбома", + "HeaderContinueWatching": "Продолжить просмотр", + "HeaderFavoriteAlbums": "Избранные альбомы", + "HeaderFavoriteArtists": "Избранные исполнители", + "HeaderFavoriteEpisodes": "Избранные эпизоды", + "HeaderFavoriteShows": "Избранные сериалы", + "HeaderFavoriteSongs": "Избранные композиции", + "HeaderLiveTV": "Эфир", + "HeaderNextUp": "Следующий", + "HeaderRecordingGroups": "Группы записей", + "HomeVideos": "Домашние видео", + "Inherit": "Наследуемое", + "ItemAddedWithName": "{0} - добавлено в медиатеку", + "ItemRemovedWithName": "{0} - изъято из медиатеки", + "LabelIpAddressValue": "IP-адрес: {0}", + "LabelRunningTimeValue": "Длительность: {0}", + "Latest": "Последние", + "MessageApplicationUpdated": "Jellyfin Server был обновлён", + "MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Конфигурация сервера (раздел {0}) была обновлена", + "MessageServerConfigurationUpdated": "Конфигурация сервера была обновлена", + "MixedContent": "Смешанное содержание", + "Movies": "Фильмы", + "Music": "Музыка", + "MusicVideos": "Муз. видео", + "NameInstallFailed": "Установка {0} неудачна", + "NameSeasonNumber": "Сезон {0}", + "NameSeasonUnknown": "Сезон не опознан", + "NewVersionIsAvailable": "Новая версия Jellyfin Server доступна для загрузки.", + "NotificationOptionApplicationUpdateAvailable": "Имеется обновление приложения", + "NotificationOptionApplicationUpdateInstalled": "Обновление приложения установлено", + "NotificationOptionAudioPlayback": "Воспроизведение аудио запущено", + "NotificationOptionAudioPlaybackStopped": "Воспроизведение аудио остановлено", + "NotificationOptionCameraImageUploaded": "Изображения с камеры загружены", + "NotificationOptionInstallationFailed": "Сбой установки", + "NotificationOptionNewLibraryContent": "Новое содержимое добавлено", + "NotificationOptionPluginError": "Сбой плагина", + "NotificationOptionPluginInstalled": "Плагин установлен", + "NotificationOptionPluginUninstalled": "Плагин удалён", + "NotificationOptionPluginUpdateInstalled": "Обновление плагина установлено", + "NotificationOptionServerRestartRequired": "Требуется перезапуск сервера", + "NotificationOptionTaskFailed": "Сбой назначенной задачи", + "NotificationOptionUserLockedOut": "Пользователь заблокирован", + "NotificationOptionVideoPlayback": "Воспроизведение видео запущено", + "NotificationOptionVideoPlaybackStopped": "Воспроизведение видео остановлено", + "Photos": "Фото", + "Playlists": "Плей-листы", + "Plugin": "Плагин", + "PluginInstalledWithName": "{0} - было установлено", + "PluginUninstalledWithName": "{0} - было удалено", + "PluginUpdatedWithName": "{0} - было обновлено", + "ProviderValue": "Поставщик: {0}", + "ScheduledTaskFailedWithName": "{0} - неудачна", + "ScheduledTaskStartedWithName": "{0} - запущена", + "ServerNameNeedsToBeRestarted": "Необходим перезапуск {0}", + "Shows": "Сериалы", + "Songs": "Композиции", + "StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.", + "SubtitleDownloadFailureFromForItem": "Субтитры к {1} не удалось загрузить с {0}", + "Sync": "Синхронизация", + "System": "Система", + "TvShows": "Телесериалы", + "User": "Пользователь", + "UserCreatedWithName": "Пользователь {0} был создан", + "UserDeletedWithName": "Пользователь {0} был удалён", + "UserDownloadingItemWithValues": "{0} загружает {1}", + "UserLockedOutWithName": "Пользователь {0} был заблокирован", + "UserOfflineFromDevice": "{0} отключился с {1}", + "UserOnlineFromDevice": "{0} подключился с {1}", + "UserPasswordChangedWithName": "Пароль пользователя {0} был изменён", + "UserPolicyUpdatedWithName": "Политики пользователя {0} были обновлены", + "UserStartedPlayingItemWithValues": "{0} - воспроизведение «{1}» на {2}", + "UserStoppedPlayingItemWithValues": "{0} - воспроизведение остановлено «{1}» на {2}", + "ValueHasBeenAddedToLibrary": "{0} добавлено в медиатеку", + "ValueSpecialEpisodeName": "Спецэпизод - {0}", + "VersionNumber": "Версия {0}", + "TaskDownloadMissingSubtitles": "Загрузка отсутствующих субтитров", + "TaskRefreshChannels": "Обновление каналов", + "TaskCleanTranscode": "Очистка каталога перекодировки", + "TaskUpdatePlugins": "Обновление плагинов", + "TaskRefreshPeople": "Обновление информации о персонах", + "TaskCleanLogs": "Очистка каталога журналов", + "TaskRefreshLibrary": "Сканирование медиатеки", + "TaskRefreshChapterImages": "Извлечение изображений сцен", + "TaskCleanCache": "Очистка каталога кеша", + "TasksChannelsCategory": "Интернет-каналы", + "TasksApplicationCategory": "Приложение", + "TasksLibraryCategory": "Медиатека", + "TasksMaintenanceCategory": "Обслуживание", + "TaskDownloadMissingSubtitlesDescription": "Выполняется поиск в Интернете отсутствующих субтитров на основе конфигурации метаданных.", + "TaskRefreshChannelsDescription": "Обновляются данные интернет-каналов.", + "TaskCleanTranscodeDescription": "Удаляются файлы перекодировки старше одного дня.", + "TaskUpdatePluginsDescription": "Загружаются и устанавливаются обновления для плагинов, у которых включено автоматическое обновление.", + "TaskRefreshPeopleDescription": "Обновляются метаданные для актёров и режиссёров в медиатеке.", + "TaskCleanLogsDescription": "Удаляются файлы журнала, возраст которых превышает {0} дн(я/ей).", + "TaskRefreshLibraryDescription": "Сканируется медиатека на новые файлы и обновляются метаданные.", + "TaskRefreshChapterImagesDescription": "Создаются эскизы для видео, которые содержат сцены.", + "TaskCleanCacheDescription": "Удаляются файлы кэша, которые больше не нужны системе.", + "TaskCleanActivityLogDescription": "Удаляет записи журнала активности старше установленного возраста.", + "TaskCleanActivityLog": "Очистка журнала активности", + "Undefined": "Не определено", + "Forced": "Принудительно", + "Default": "По умолчанию", + "TaskOptimizeDatabaseDescription": "Сжимает базу данных и вырезает свободные места. Выполнение этой задачи после сканирования библиотеки или внесения других изменений, предполагающих модификации базы данных, может повысить производительность.", + "TaskOptimizeDatabase": "Оптимизация базы данных", + "TaskKeyframeExtractorDescription": "Извлекаются ключевые кадры из видеофайлов для создания более точных списков плей-листов HLS. Эта задача может выполняться в течение длительного времени.", + "TaskKeyframeExtractor": "Извлечение ключевых кадров", + "External": "Внешние", + "HearingImpaired": "Для слабослышащих", + "TaskRefreshTrickplayImages": "Сгенерировать изображения для Trickplay", + "TaskRefreshTrickplayImagesDescription": "Создает предпросмотры для Trickplay для видео в библиотеках, где эта функция включена.", + "TaskCleanCollectionsAndPlaylists": "Очистка коллекций и списков воспроизведения", + "TaskCleanCollectionsAndPlaylistsDescription": "Удаляет элементы из коллекций и списков воспроизведения, которые больше не существуют.", + "TaskAudioNormalization": "Нормализация звука", + "TaskAudioNormalizationDescription": "Сканирует файлы на наличие данных о нормализации звука.", + "TaskDownloadMissingLyrics": "Загрузить недостающий текст", + "TaskDownloadMissingLyricsDescription": "Загружает текст песен", + "TaskMoveTrickplayImages": "Перенесение местоположения изображений Trickplay", + "TaskExtractMediaSegments": "Сканирование медиасегментов", + "TaskExtractMediaSegmentsDescription": "Извлекает или получает медиасегменты из плагинов MediaSegment.", + "TaskMoveTrickplayImagesDescription": "Перемещает существующие файлы trickplay в соответствии с настройками медиатеки.", + "CleanupUserDataTask": "Задача очистки пользовательских данных", + "CleanupUserDataTaskDescription": "Очищает все пользовательские данные (состояние просмотра, статус избранного и т.д.) с медиа, отсутствующих по меньшей мере в течение 90 дней." +} diff --git a/Emby.Server.Implementations/Localization/Core/si.json b/Emby.Server.Implementations/Localization/Core/si.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/si.json @@ -0,0 +1 @@ +{} diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json new file mode 100644 index 00000000..7c8d8604 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/sk.json @@ -0,0 +1,141 @@ +{ + "Albums": "Albumy", + "AppDeviceValues": "Aplikácia: {0}, Zariadenie: {1}", + "Application": "Aplikácia", + "Artists": "Interpreti", + "AuthenticationSucceededWithUserName": "{0} úspešne overený", + "Books": "Knihy", + "CameraImageUploadedFrom": "Z {0} bola nahraná nová fotografia", + "Channels": "Kanály", + "ChapterNameValue": "Kapitola {0}", + "Collections": "Kolekcie", + "DeviceOfflineWithName": "{0} sa odpojil", + "DeviceOnlineWithName": "{0} je pripojený", + "FailedLoginAttemptWithUserName": "Neúspešný pokus o prihlásenie z {0}", + "Favorites": "Obľúbené", + "Folders": "Priečinky", + "Genres": "Žánre", + "HeaderAlbumArtists": "Interpreti albumu", + "HeaderContinueWatching": "Pokračovať v pozeraní", + "HeaderFavoriteAlbums": "Obľúbené albumy", + "HeaderFavoriteArtists": "Obľúbení interpreti", + "HeaderFavoriteEpisodes": "Obľúbené epizódy", + "HeaderFavoriteShows": "Obľúbené seriály", + "HeaderFavoriteSongs": "Obľúbené skladby", + "HeaderLiveTV": "Živá TV", + "HeaderNextUp": "Nasleduje", + "HeaderRecordingGroups": "Skupiny nahrávok", + "HomeVideos": "Domáce videá", + "Inherit": "Zdediť", + "ItemAddedWithName": "{0} bol pridaný do knižnice", + "ItemRemovedWithName": "{0} bol odstránený z knižnice", + "LabelIpAddressValue": "IP adresa: {0}", + "LabelRunningTimeValue": "Dĺžka: {0}", + "Latest": "Najnovšie", + "MessageApplicationUpdated": "Jellyfin Server bol aktualizovaný", + "MessageApplicationUpdatedTo": "Jellyfin Server bol aktualizovaný na verziu {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Sekcia {0} konfigurácie servera bola aktualizovaná", + "MessageServerConfigurationUpdated": "Konfigurácia servera bola aktualizovaná", + "MixedContent": "Zmiešaný obsah", + "Movies": "Filmy", + "Music": "Hudba", + "MusicVideos": "Hudobné videoklipy", + "NameInstallFailed": "Inštalácia {0} zlyhala", + "NameSeasonNumber": "Séria {0}", + "NameSeasonUnknown": "Neznáma séria", + "NewVersionIsAvailable": "Nová verzia Jellyfin Serveru je dostupná na stiahnutie.", + "NotificationOptionApplicationUpdateAvailable": "Je dostupná aktualizácia aplikácie", + "NotificationOptionApplicationUpdateInstalled": "Aktualizácia aplikácie bola nainštalovaná", + "NotificationOptionAudioPlayback": "Prehrávanie zvuku bolo spustené", + "NotificationOptionAudioPlaybackStopped": "Prehrávanie zvuku bolo zastavené", + "NotificationOptionCameraImageUploaded": "Obrázok z fotoaparátu bol nahraný", + "NotificationOptionInstallationFailed": "Chyba inštalácie", + "NotificationOptionNewLibraryContent": "Bol pridaný nový obsah", + "NotificationOptionPluginError": "Chyba zásuvného modulu", + "NotificationOptionPluginInstalled": "Bol nainštalovaný zásuvný modul", + "NotificationOptionPluginUninstalled": "Zásuvný modul bol odinštalovaný", + "NotificationOptionPluginUpdateInstalled": "Bola nainštalovaná aktualizácia zásuvného modulu", + "NotificationOptionServerRestartRequired": "Vyžaduje sa reštart servera", + "NotificationOptionTaskFailed": "Naplánovaná úloha zlyhala", + "NotificationOptionUserLockedOut": "Používateľ je uzamknutý", + "NotificationOptionVideoPlayback": "Spustené prehrávanie videa", + "NotificationOptionVideoPlaybackStopped": "Zastavené prehrávanie videa", + "Photos": "Fotky", + "Playlists": "Playlisty", + "Plugin": "Zásuvný modul", + "PluginInstalledWithName": "{0} bol nainštalovaný", + "PluginUninstalledWithName": "{0} bol odinštalovaný", + "PluginUpdatedWithName": "{0} bol aktualizovaný", + "ProviderValue": "Poskytovateľ: {0}", + "ScheduledTaskFailedWithName": "{0} zlyhalo", + "ScheduledTaskStartedWithName": "{0} zahájených", + "ServerNameNeedsToBeRestarted": "{0} vyžaduje reštart", + "Shows": "Seriály", + "Songs": "Skladby", + "StartupEmbyServerIsLoading": "Jellyfin Server sa spúšťa. Prosím, skúste to o chvíľu znova.", + "SubtitleDownloadFailureFromForItem": "Sťahovanie titulkov z {0} pre {1} zlyhalo", + "Sync": "Synchronizácia", + "System": "Systém", + "TvShows": "TV seriály", + "User": "Používateľ", + "UserCreatedWithName": "Používateľ {0} bol vytvorený", + "UserDeletedWithName": "Používateľ {0} bol vymazaný", + "UserDownloadingItemWithValues": "{0} sťahuje {1}", + "UserLockedOutWithName": "Používateľ {0} bol vymknutý", + "UserOfflineFromDevice": "{0} sa odpojil od {1}", + "UserOnlineFromDevice": "{0} je online z {1}", + "UserPasswordChangedWithName": "Heslo používateľa {0} bolo zmenené", + "UserPolicyUpdatedWithName": "Používateľské zásady pre {0} boli aktualizované", + "UserStartedPlayingItemWithValues": "{0} spustil prehrávanie {1} na {2}", + "UserStoppedPlayingItemWithValues": "{0} ukončil prehrávanie {1} na {2}", + "ValueHasBeenAddedToLibrary": "{0} bol pridaný do vašej knižnice médií", + "ValueSpecialEpisodeName": "Špeciál - {0}", + "VersionNumber": "Verzia {0}", + "TaskDownloadMissingSubtitlesDescription": "Vyhľadá na internete chýbajúce titulky podľa toho, ako sú nakonfigurované metadáta.", + "TaskDownloadMissingSubtitles": "Stiahnuť chýbajúce titulky", + "TaskRefreshChannelsDescription": "Obnoví informácie o internetových kanáloch.", + "TaskRefreshChannels": "Obnoviť kanály", + "TaskCleanTranscodeDescription": "Vymaže prekódované súbory, ktoré sú staršie ako jeden deň.", + "TaskCleanTranscode": "Vyčistiť priečinok pre prekódovanie", + "TaskUpdatePluginsDescription": "Stiahne a nainštaluje aktualizácie pre zásuvné moduly, ktoré sú nastavené tak, aby sa aktualizovali automaticky.", + "TaskUpdatePlugins": "Aktualizovať zásuvné moduly", + "TaskRefreshPeopleDescription": "Aktualizuje metadáta pre hercov a režisérov vo vašej mediálnej knižnici.", + "TaskRefreshPeople": "Obnoviť osoby", + "TaskCleanLogsDescription": "Vymaže log súbory, ktoré sú staršie ako {0} deň/dni/dní.", + "TaskCleanLogs": "Vyčistiť priečinok s logmi", + "TaskRefreshLibraryDescription": "Hľadá vo vašej mediálnej knižnici nové súbory a obnovuje metadáta.", + "TaskRefreshLibrary": "Prehľadávať knižnicu medií", + "TaskRefreshChapterImagesDescription": "Vytvorí náhľady pre videá, ktoré majú kapitoly.", + "TaskRefreshChapterImages": "Extrahovať obrázky kapitol", + "TaskCleanCacheDescription": "Vymaže súbory vyrovnávacej pamäte, ktoré už nie sú potrebné pre systém.", + "TaskCleanCache": "Vyčistiť priečinok vyrovnávacej pamäte", + "TasksChannelsCategory": "Internetové kanály", + "TasksApplicationCategory": "Aplikácia", + "TasksLibraryCategory": "Knižnica", + "TasksMaintenanceCategory": "Údržba", + "TaskCleanActivityLogDescription": "Vymaže záznamy aktivít v logu, ktoré sú staršie ako zadaná doba.", + "TaskCleanActivityLog": "Vyčistiť log aktivít", + "Undefined": "Nedefinované", + "Forced": "Vynútené", + "Default": "Predvolené", + "TaskOptimizeDatabaseDescription": "Zmenší databázu a odstráni prázdne miesto. Spustenie tejto úlohy po skenovaní knižnice alebo po iných zmenách zahŕňajúcich úpravy databáze môže zlepšiť výkon.", + "TaskOptimizeDatabase": "Optimalizovať databázu", + "TaskKeyframeExtractorDescription": "Extrahuje kľúčové snímky z video súborov na vytvorenie presnejších HLS playlistov. Táto úloha môže trvať dlhšiu dobu.", + "TaskKeyframeExtractor": "Extraktor kľúčových snímkov", + "External": "Externé", + "HearingImpaired": "Sluchovo postihnutí", + "TaskRefreshTrickplayImages": "Generovanie obrázkov Trickplay", + "TaskRefreshTrickplayImagesDescription": "Vytvára trickplay náhľady pre videá v povolených knižniciach.", + "TaskCleanCollectionsAndPlaylists": "Vyčistiť kolekcie a playlisty", + "TaskCleanCollectionsAndPlaylistsDescription": "Odstráni položky z kolekcií a playlistov, ktoré už neexistujú.", + "TaskAudioNormalization": "Normalizácia zvuku", + "TaskAudioNormalizationDescription": "Skenovať súbory za účelom normalizácie zvuku.", + "TaskExtractMediaSegments": "Skenovanie segmentov médií", + "TaskExtractMediaSegmentsDescription": "Extrahuje alebo získava segmenty médií zo zásuvných modulov s povolenou funkciou MediaSegment.", + "TaskMoveTrickplayImages": "Presunúť umiestnenie obrázkov Trickplay", + "TaskMoveTrickplayImagesDescription": "Presunie existujúce súbory Trickplay podľa nastavení knižnice.", + "TaskDownloadMissingLyrics": "Stiahnuť chýbajúce texty piesní", + "TaskDownloadMissingLyricsDescription": "Stiahne texty pre piesne", + "CleanupUserDataTask": "Prečistiť používateľské dáta", + "CleanupUserDataTaskDescription": "Vyčistí všetky dáta používateľa (stav sledovania, stav obľúbených atď.) z médií, ktoré už neexistujú aspoň 90 dní." +} diff --git a/Emby.Server.Implementations/Localization/Core/sl-SI.json b/Emby.Server.Implementations/Localization/Core/sl-SI.json new file mode 100644 index 00000000..7c7c88e2 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/sl-SI.json @@ -0,0 +1,141 @@ +{ + "Albums": "Albumi", + "AppDeviceValues": "Aplikacija: {0}, Naprava: {1}", + "Application": "Aplikacija", + "Artists": "Izvajalci", + "AuthenticationSucceededWithUserName": "{0} se je uspešno prijavil/a", + "Books": "Knjige", + "CameraImageUploadedFrom": "Nova fotografija je bila naložena iz {0}", + "Channels": "Kanali", + "ChapterNameValue": "Poglavje {0}", + "Collections": "Zbirke", + "DeviceOfflineWithName": "{0} je prekinil povezavo", + "DeviceOnlineWithName": "{0} je povezan", + "FailedLoginAttemptWithUserName": "Neuspešen poskus prijave z {0}", + "Favorites": "Priljubljeno", + "Folders": "Mape", + "Genres": "Zvrsti", + "HeaderAlbumArtists": "Izvajalci albuma", + "HeaderContinueWatching": "Nadaljuj ogled", + "HeaderFavoriteAlbums": "Priljubljeni albumi", + "HeaderFavoriteArtists": "Priljubljeni izvajalci", + "HeaderFavoriteEpisodes": "Priljubljene epizode", + "HeaderFavoriteShows": "Priljubljene serije", + "HeaderFavoriteSongs": "Priljubljene pesmi", + "HeaderLiveTV": "TV v živo", + "HeaderNextUp": "Sledi", + "HeaderRecordingGroups": "Zbirke posnetkov", + "HomeVideos": "Domači posnetki", + "Inherit": "Podeduj", + "ItemAddedWithName": "{0} je dodan v knjižnico", + "ItemRemovedWithName": "{0} je bil odstranjen iz knjižnice", + "LabelIpAddressValue": "IP naslov: {0}", + "LabelRunningTimeValue": "Čas trajanja: {0}", + "Latest": "Najnovejše", + "MessageApplicationUpdated": "Jellyfin strežnik je bil posodobljen", + "MessageApplicationUpdatedTo": "Jellyfin strežnik je bil posodobljen na {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Oddelek nastavitev {0} je bil posodobljen", + "MessageServerConfigurationUpdated": "Nastavitve strežnika so bile posodobljene", + "MixedContent": "Mešane vsebine", + "Movies": "Filmi", + "Music": "Glasba", + "MusicVideos": "Glasbeni videi", + "NameInstallFailed": "{0} namestitev neuspešna", + "NameSeasonNumber": "Sezona {0}", + "NameSeasonUnknown": "Neznana sezona", + "NewVersionIsAvailable": "Nova različica Jellyfin strežnika je na voljo za prenos.", + "NotificationOptionApplicationUpdateAvailable": "Posodobitev aplikacije je na voljo", + "NotificationOptionApplicationUpdateInstalled": "Posodobitev aplikacije je bila nameščena", + "NotificationOptionAudioPlayback": "Predvajanje zvoka se je začelo", + "NotificationOptionAudioPlaybackStopped": "Predvajanje zvoka se je ustavilo", + "NotificationOptionCameraImageUploaded": "Fotografija naložena", + "NotificationOptionInstallationFailed": "Namestitev neuspešna", + "NotificationOptionNewLibraryContent": "Nove vsebine dodane", + "NotificationOptionPluginError": "Napaka dodatka", + "NotificationOptionPluginInstalled": "Dodatek nameščen", + "NotificationOptionPluginUninstalled": "Dodatek odstranjen", + "NotificationOptionPluginUpdateInstalled": "Posodobitev dodatka nameščena", + "NotificationOptionServerRestartRequired": "Potreben je ponovni zagon strežnika", + "NotificationOptionTaskFailed": "Načrtovano opravilo neuspešno", + "NotificationOptionUserLockedOut": "Uporabnik zaklenjen", + "NotificationOptionVideoPlayback": "Predvajanje videa se je začelo", + "NotificationOptionVideoPlaybackStopped": "Predvajanje videa se je ustavilo", + "Photos": "Fotografije", + "Playlists": "Seznami predvajanja", + "Plugin": "Dodatek", + "PluginInstalledWithName": "{0} je bil nameščen", + "PluginUninstalledWithName": "{0} je bil odstranjen", + "PluginUpdatedWithName": "{0} je bil posodobljen", + "ProviderValue": "Ponudnik: {0}", + "ScheduledTaskFailedWithName": "{0} ni uspelo", + "ScheduledTaskStartedWithName": "{0} začeto", + "ServerNameNeedsToBeRestarted": "{0} mora biti ponovno zagnan", + "Shows": "Serije", + "Songs": "Pesmi", + "StartupEmbyServerIsLoading": "Jellyfin strežnik se zaganja. Poskusite ponovno kasneje.", + "SubtitleDownloadFailureFromForItem": "Neuspešen prenos podnapisov iz {0} za {1}", + "Sync": "Sinhroniziraj", + "System": "Sistem", + "TvShows": "TV serije", + "User": "Uporabnik", + "UserCreatedWithName": "Uporabnik {0} je bil ustvarjen", + "UserDeletedWithName": "Uporabnik {0} je bil izbrisan", + "UserDownloadingItemWithValues": "{0} prenaša {1}", + "UserLockedOutWithName": "Uporabnik {0} je bil zaklenjen", + "UserOfflineFromDevice": "{0} je prekinil povezavo z {1}", + "UserOnlineFromDevice": "{0} je aktiven na {1}", + "UserPasswordChangedWithName": "Geslo za uporabnika {0} je bilo spremenjeno", + "UserPolicyUpdatedWithName": "Pravilnik uporabe je bil posodobljen za uporabnika {0}", + "UserStartedPlayingItemWithValues": "{0} predvaja {1} na {2}", + "UserStoppedPlayingItemWithValues": "{0} je nehal predvajati {1} na {2}", + "ValueHasBeenAddedToLibrary": "{0} je bil dodan vaši knjižnici", + "ValueSpecialEpisodeName": "Posebna epizoda - {0}", + "VersionNumber": "Različica {0}", + "TaskDownloadMissingSubtitles": "Prenesi manjkajoče podnapise", + "TaskRefreshChannelsDescription": "Osveži podatke spletnih kanalov.", + "TaskRefreshChannels": "Osveži kanale", + "TaskCleanTranscodeDescription": "Izbriše več kot dan stare datoteke prekodiranja.", + "TaskCleanTranscode": "Počisti mapo prekodiranja", + "TaskUpdatePluginsDescription": "Prenese in namesti posodobitve za dodatke, ki imajo omogočene samodejne posodobitve.", + "TaskUpdatePlugins": "Posodobi dodatke", + "TaskRefreshPeopleDescription": "Osveži metapodatke za igralce in režiserje v vaši knjižnici.", + "TaskRefreshPeople": "Osveži osebe", + "TaskCleanLogsDescription": "Izbriše dnevniške datoteke starejše od {0} dni.", + "TaskCleanLogs": "Počisti mapo dnevnikov", + "TaskRefreshLibraryDescription": "Preišče vašo knjižnico za nove datoteke in osveži metapodatke.", + "TaskRefreshLibrary": "Preišči knjižnico predstavnosti", + "TaskRefreshChapterImagesDescription": "Ustvari sličice za poglavja videoposnetkov.", + "TaskRefreshChapterImages": "Izvleči slike poglavij", + "TaskCleanCacheDescription": "Izbriše predpomnjene datoteke, ki niso več potrebne.", + "TaskCleanCache": "Počisti mapo predpomnilnika", + "TasksChannelsCategory": "Spletni kanali", + "TasksApplicationCategory": "Aplikacija", + "TasksLibraryCategory": "Knjižnica", + "TasksMaintenanceCategory": "Vzdrževanje", + "TaskDownloadMissingSubtitlesDescription": "Na podlagi nastavitev metapodatkov poišče manjkajoče podnapise na internetu.", + "TaskCleanActivityLogDescription": "Počisti zapise v dnevniku aktivnosti starejše od nastavljenega časa.", + "TaskCleanActivityLog": "Počisti dnevnik aktivnosti", + "Undefined": "Nedoločen", + "Forced": "Prisilno", + "Default": "Privzeto", + "TaskOptimizeDatabaseDescription": "Stisne bazo podatkov in uredi prazen prostor. Zagon tega opravila po iskanju predstavnosti ali drugih spremembah ki vplivajo na bazo podatkov lahko izboljša hitrost delovanja.", + "TaskOptimizeDatabase": "Optimiziraj bazo podatkov", + "TaskKeyframeExtractor": "Ekstraktor ključnih sličic", + "External": "Zunanji", + "TaskKeyframeExtractorDescription": "Iz video datoteke Izvleče ključne sličice, da ustvari bolj natančne sezname predvajanja HLS. Proces lahko traja dolgo časa.", + "HearingImpaired": "Oslabljen sluh", + "TaskRefreshTrickplayImages": "Ustvari Trickplay slike", + "TaskRefreshTrickplayImagesDescription": "Ustvari trickplay predoglede za posnetke v omogočenih knjižnicah.", + "TaskExtractMediaSegmentsDescription": "Ekstrahira ali pridobi medijske segmente iz vtičnikov, ki podpirajo MediaSegment.", + "TaskMoveTrickplayImagesDescription": "Premakne obstoječe datoteke trickplay v skladu z nastavitvami knjižnice.", + "TaskExtractMediaSegments": "Skeniranje segmentov v medijih", + "TaskMoveTrickplayImages": "Preseli lokacijo Trickplay slik", + "TaskDownloadMissingLyrics": "Prenesi manjkajoča besedila pesmi", + "TaskDownloadMissingLyricsDescription": "Prenesi besedila za pesmi", + "TaskCleanCollectionsAndPlaylists": "Počisti zbirke in sezname predvajanja", + "TaskAudioNormalization": "Normalizacija zvoka", + "TaskAudioNormalizationDescription": "Pregled datotek za podatke o normalizaciji zvoka.", + "TaskCleanCollectionsAndPlaylistsDescription": "Odstrani elemente iz zbirk in seznamov predvajanja, ki ne obstajajo več.", + "CleanupUserDataTask": "Čiščenje uporabniških podatkov", + "CleanupUserDataTaskDescription": "Izbriše vse uporabniške podatke (stanje ogleda, priljubljene itd.) za vsebine, ki že več kot 90 dni niso na voljo." +} diff --git a/Emby.Server.Implementations/Localization/Core/sn.json b/Emby.Server.Implementations/Localization/Core/sn.json new file mode 100644 index 00000000..74720e76 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/sn.json @@ -0,0 +1,28 @@ +{ + "HeaderAlbumArtists": "Vaimbi vemadambarefu", + "HeaderContinueWatching": "Simudzira kuona", + "HeaderFavoriteSongs": "Nziyo dzaunofarira", + "Albums": "Dambarefu", + "AppDeviceValues": "Apu: {0}, Dhivhaisi: {1}", + "Application": "Purogiramu", + "Artists": "Vaimbi", + "AuthenticationSucceededWithUserName": "apinda", + "Books": "Mabhuku", + "CameraImageUploadedFrom": "Mufananidzo mutsva vabva pakamera {0}", + "Channels": "Machanewo", + "ChapterNameValue": "Chikamu {0}", + "Collections": "Akafanana", + "Default": "Zvakasarudzwa Kare", + "DeviceOfflineWithName": "{0} haasisipo", + "DeviceOnlineWithName": "{0} aripo", + "External": "Zvekunze", + "FailedLoginAttemptWithUserName": "Vatadza kuloga chimboedza kushandisa {0}", + "Favorites": "Zvaunofarira", + "Folders": "Mafoodha", + "Forced": "Zvekumanikidzira", + "Genres": "Mhando", + "HeaderFavoriteAlbums": "Madambarefu aunofarira", + "HeaderFavoriteArtists": "Vaimbi vaunofarira", + "HeaderFavoriteEpisodes": "Maepisodhi aunofarira", + "HeaderFavoriteShows": "Masirisi aunofarira" +} diff --git a/Emby.Server.Implementations/Localization/Core/sq.json b/Emby.Server.Implementations/Localization/Core/sq.json new file mode 100644 index 00000000..8f0079c9 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/sq.json @@ -0,0 +1,141 @@ +{ + "MessageApplicationUpdatedTo": "Serveri Jellyfin u përditesua në versionin {0}", + "Inherit": "Trashgimi", + "TaskDownloadMissingSubtitlesDescription": "Kërkon në internet për titra që mungojnë bazuar tek konfigurimi i metadata-ve.", + "TaskDownloadMissingSubtitles": "Shkarko titra që mungojnë", + "TaskRefreshChannelsDescription": "Rifreskon informacionin e kanaleve të internetit.", + "TaskRefreshChannels": "Rifresko Kanalet", + "TaskCleanTranscodeDescription": "Fshin skedarët e transkodimit që janë më të vjetër se një ditë.", + "TaskCleanTranscode": "Fshi dosjen e transkodimit", + "TaskUpdatePluginsDescription": "Shkarkon dhe instalon përditësimi për plugin që janë konfiguruar të përditësohen automatikisht.", + "TaskUpdatePlugins": "Përditëso Plugin", + "TaskRefreshPeopleDescription": "Përditëson metadata të aktorëve dhe regjizorëve në librarinë tuaj.", + "TaskRefreshPeople": "Rifresko aktorët", + "TaskCleanLogsDescription": "Fshin skëdarët log që janë më të vjetër se {0} ditë.", + "TaskCleanLogs": "Fshi dosjen Log", + "TaskRefreshLibraryDescription": "Skanon librarinë media për skedarë të rinj dhe rifreskon metadata.", + "TaskRefreshLibrary": "Skano librarinë media", + "TaskRefreshChapterImagesDescription": "Krijon imazh për videot që kanë kapituj.", + "TaskRefreshChapterImages": "Ekstrakto Imazhet e Kapitullit", + "TaskCleanCacheDescription": "Fshi skedarët e cache-s që nuk i duhen më sistemit.", + "TaskCleanCache": "Pastro memorjen cache", + "TasksChannelsCategory": "Kanalet nga interneti", + "TasksApplicationCategory": "Aplikacioni", + "TasksLibraryCategory": "Libraria", + "TasksMaintenanceCategory": "Mirëmbajtje", + "VersionNumber": "Versioni {0}", + "ValueSpecialEpisodeName": "Speciale - {0}", + "ValueHasBeenAddedToLibrary": "{0} u shtua tek libraria juaj", + "UserStoppedPlayingItemWithValues": "{0} mbaroi së shikuari {1} tek {2}", + "UserStartedPlayingItemWithValues": "{0} po shikon {1} tek {2}", + "UserPolicyUpdatedWithName": "Politika e përdoruesit u përditësua për {0}", + "UserPasswordChangedWithName": "Fjalëkalimi u ndryshua për përdoruesin {0}", + "UserOnlineFromDevice": "{0} është në linjë nga {1}", + "UserOfflineFromDevice": "{0} u shkëput nga {1}", + "UserLockedOutWithName": "Përdoruesi {0} u përjashtua", + "UserDownloadingItemWithValues": "{0} po shkarkon {1}", + "UserDeletedWithName": "Përdoruesi {0} u fshi", + "UserCreatedWithName": "Përdoruesi {0} u krijua", + "User": "Përdoruesi", + "TvShows": "Seriale TV", + "System": "Sistemi", + "Sync": "Sinkronizo", + "SubtitleDownloadFailureFromForItem": "Titrat deshtuan të shkarkohen nga {0} për {1}", + "StartupEmbyServerIsLoading": "Serveri Jellyfin po ngarkohet. Ju lutemi provoni përseri pas pak.", + "Songs": "Këngët", + "Shows": "Serialet", + "ServerNameNeedsToBeRestarted": "{0} duhet të ristartoj", + "ScheduledTaskStartedWithName": "{0} filloi", + "ScheduledTaskFailedWithName": "{0} dështoi", + "ProviderValue": "Ofruesi: {0}", + "PluginUpdatedWithName": "{0} u përditësua", + "PluginUninstalledWithName": "{0} u çinstalua", + "PluginInstalledWithName": "{0} u instalua", + "Plugin": "Plugin", + "Playlists": "Listat për luajtje", + "Photos": "Fotografitë", + "NotificationOptionVideoPlaybackStopped": "Luajtja e videos ndaloi", + "NotificationOptionVideoPlayback": "Luajtja e videos filloi", + "NotificationOptionUserLockedOut": "Përdoruesi u përjashtua", + "NotificationOptionTaskFailed": "Ushtrimi i planifikuar dështoi", + "NotificationOptionServerRestartRequired": "Kërkohet ristartim i serverit", + "NotificationOptionPluginUpdateInstalled": "Përditësimi i plugin u instalua", + "NotificationOptionPluginUninstalled": "Plugin u çinstalua", + "NotificationOptionPluginInstalled": "Plugin u instalua", + "NotificationOptionPluginError": "Plugin dështoi", + "NotificationOptionNewLibraryContent": "Një përmbajtje e re u shtua", + "NotificationOptionInstallationFailed": "Instalimi dështoi", + "NotificationOptionCameraImageUploaded": "Fotoja nga kamera u ngarkua", + "NotificationOptionAudioPlaybackStopped": "Luajtja e audios ndaloi", + "NotificationOptionAudioPlayback": "Luajtja e audios filloi", + "NotificationOptionApplicationUpdateInstalled": "Përditësimi i aplikacionit u instalua", + "NotificationOptionApplicationUpdateAvailable": "Një perditësim i aplikacionit është gati", + "NewVersionIsAvailable": "Një version i ri i Jellyfin është gati për tu shkarkuar.", + "NameSeasonUnknown": "Sezon i panjohur", + "NameSeasonNumber": "Sezoni {0}", + "NameInstallFailed": "Instalimi i {0} dështoi", + "MusicVideos": "Video Muzikore", + "Music": "Muzikë", + "Movies": "Filmat", + "MixedContent": "Përmbajtje e përzier", + "MessageServerConfigurationUpdated": "Konfigurimet e serverit u përditësuan", + "MessageNamedServerConfigurationUpdatedWithValue": "Seksioni i konfigurimit të serverit {0} u përditësua", + "MessageApplicationUpdated": "Serveri Jellyfin u përditësua", + "Latest": "Të fundit", + "LabelRunningTimeValue": "Kohëzgjatja: {0}", + "LabelIpAddressValue": "Adresa IP: {0}", + "ItemRemovedWithName": "{0} u fshi nga libraria", + "ItemAddedWithName": "{0} u shtua tek libraria", + "HomeVideos": "Video personale", + "HeaderRecordingGroups": "Grupet e regjistrimit", + "HeaderNextUp": "Në vazhdim", + "HeaderLiveTV": "TV Live", + "HeaderFavoriteSongs": "Kënget e preferuara", + "HeaderFavoriteShows": "Serialet e preferuar", + "HeaderFavoriteEpisodes": "Episodet e preferuar", + "HeaderFavoriteArtists": "Artistët e preferuar", + "HeaderFavoriteAlbums": "Albumet e preferuar", + "HeaderContinueWatching": "Vazhdo të shikosh", + "HeaderAlbumArtists": "Artistët e albumeve", + "Genres": "Zhanret", + "Folders": "Skedarët", + "Favorites": "Të preferuarat", + "FailedLoginAttemptWithUserName": "Përpjekja për hyrje dështoi nga {0}", + "DeviceOnlineWithName": "{0} u lidh", + "DeviceOfflineWithName": "{0} u shkëput", + "Collections": "Koleksionet", + "ChapterNameValue": "Kapituj", + "Channels": "Kanalet", + "CameraImageUploadedFrom": "Një foto e re nga kamera u ngarkua nga {0}", + "Books": "Librat", + "AuthenticationSucceededWithUserName": "{0} u identifikua me sukses", + "Artists": "Artistët", + "Application": "Aplikacioni", + "AppDeviceValues": "Aplikacioni: {0}, Pajisja: {1}", + "Albums": "Albumet", + "TaskCleanActivityLogDescription": "Pastro të dhënat mbi aktivitetin më të vjetra sesa koha e përcaktuar.", + "TaskCleanActivityLog": "Pastro të dhënat mbi aktivitetin", + "Undefined": "I papërcaktuar", + "Forced": "I detyruar", + "Default": "Parazgjedhur", + "TaskOptimizeDatabaseDescription": "Kompakton bazën e të dhënave dhe shkurton hapësirën e lirë. Drejtimi i kësaj detyre pasi skanoni bibliotekën ose bëni ndryshime të tjera që nënkuptojnë modifikime të bazës së të dhënave mund të përmirësojë performancën.", + "TaskOptimizeDatabase": "Optimizo databazën", + "TaskKeyframeExtractorDescription": "Nxjerrë kornizat kryesore nga skedarët video për të krijuar lista luajtjeje më të sakta HLS. Ky veprim mund të dojë një kohë të gjatë për tu kompletuar.", + "TaskKeyframeExtractor": "Nxjerrës i kornizës kryesore", + "External": "Jashtem", + "HearingImpaired": "Dëgjimi i dëmtuar", + "TaskRefreshTrickplayImages": "Krijo Imazhe Trickplay", + "TaskRefreshTrickplayImagesDescription": "Krijon pamje paraprake për video në bibliotekat e aktivizuara.", + "TaskExtractMediaSegments": "Skanim i segmenteve të medias", + "TaskExtractMediaSegmentsDescription": "Nxjerr ose merr segmente mediaje nga shtojcat që kanë të aktivizuar MediaSegment.", + "TaskMoveTrickplayImages": "Migron vendndodhjen e imazheve Trickplay", + "TaskMoveTrickplayImagesDescription": "Zhvendos skedarët ekzistues të trickplay sipas cilësimeve të bibliotekës.", + "TaskDownloadMissingLyrics": "Shkarko tekstet e këngëve që mungojnë", + "TaskDownloadMissingLyricsDescription": "Shkarkon tekstet e këngëve", + "TaskCleanCollectionsAndPlaylists": "Pastron koleksionet dhe listat e këngëve", + "TaskCleanCollectionsAndPlaylistsDescription": "Heq elementet nga koleksionet dhe listat e këngëve që nuk ekzistojnë më.", + "TaskAudioNormalization": "Normalizimi i audios", + "TaskAudioNormalizationDescription": "Skannon skedarët për të dhëna të normalizimit të audios.", + "CleanupUserDataTaskDescription": "Pastron të gjitha të dhënat e përdorueseve (gjendja e shikimit, statusi i të preferuarave etj.) nga mediat që nuk janë më të pranishme për të paktën 90 ditë.", + "CleanupUserDataTask": "Veprim për pastrimin të dhënave të përdorueseve" +} diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json new file mode 100644 index 00000000..76a136cf --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/sr.json @@ -0,0 +1,141 @@ +{ + "UserPolicyUpdatedWithName": "Корисничке смернице ажуриране за {0}", + "NotificationOptionUserLockedOut": "Корисник закључан", + "VersionNumber": "Верзија {0}", + "ValueSpecialEpisodeName": "Специјал - {0}", + "ValueHasBeenAddedToLibrary": "{0} је додато у вашу медијску библиотеку", + "UserStoppedPlayingItemWithValues": "{0} завршио пуштање {1} на {2}", + "UserStartedPlayingItemWithValues": "{0} пушта {1} на {2}", + "UserPasswordChangedWithName": "Лозинка је промењена за корисника {0}", + "UserOnlineFromDevice": "{0} је на вези од {1}", + "UserOfflineFromDevice": "{0} је прекинуо/а везу са {1}", + "UserLockedOutWithName": "Корисник {0} је закључан", + "UserDownloadingItemWithValues": "{0} преузима {1}", + "UserDeletedWithName": "Корисник {0} је обрисан", + "UserCreatedWithName": "Корисник {0} је направљен", + "User": "Корисник", + "TvShows": "ТВ серије", + "System": "Систем", + "Sync": "Усклади", + "SubtitleDownloadFailureFromForItem": "Неуспело преузимање титлова за {1} са {0}", + "StartupEmbyServerIsLoading": "Џелифин сервер се подиже. Покушајте поново убрзо.", + "Songs": "Песме", + "Shows": "Серије", + "ServerNameNeedsToBeRestarted": "{0} треба поново покренути", + "ScheduledTaskStartedWithName": "{0} покренуто", + "ScheduledTaskFailedWithName": "{0} неуспело", + "ProviderValue": "Пружалац: {0}", + "PluginUpdatedWithName": "{0} ажуриран", + "PluginUninstalledWithName": "{0} деинсталиран", + "PluginInstalledWithName": "{0} инсталиран", + "Plugin": "Прикључак", + "Playlists": "Листе", + "Photos": "Фотографије", + "NotificationOptionVideoPlaybackStopped": "Заустављено пуштање видеа", + "NotificationOptionVideoPlayback": "Покренуто пуштање видеа", + "NotificationOptionTaskFailed": "Неуспешан заказани посао", + "NotificationOptionServerRestartRequired": "Потребан је рестарт сервера", + "NotificationOptionPluginUpdateInstalled": "Ажурирање прикључка инсталирано", + "NotificationOptionPluginUninstalled": "Прикључак уклоњен", + "NotificationOptionPluginInstalled": "Прикључак инсталиран", + "NotificationOptionPluginError": "Грешка прикључка", + "NotificationOptionNewLibraryContent": "Додат нови садржај", + "NotificationOptionInstallationFailed": "Неуспела инсталација", + "NotificationOptionCameraImageUploaded": "Слика са камере отпремљена", + "NotificationOptionAudioPlaybackStopped": "Заустављено пуштање звука", + "NotificationOptionAudioPlayback": "Покренуто пуштање звука", + "NotificationOptionApplicationUpdateInstalled": "Ажурирање инсталирано", + "NotificationOptionApplicationUpdateAvailable": "Доступно је ажурирање за апликацију", + "NewVersionIsAvailable": "Нова верзија Џелифин сервера је доступна за преузимање.", + "NameSeasonUnknown": "Непозната сезона", + "NameSeasonNumber": "Сезона {0}", + "NameInstallFailed": "Инсталација {0} није успела", + "MusicVideos": "Музички видео", + "Music": "Музика", + "Movies": "Филмови", + "MixedContent": "Мешовит садржај", + "MessageServerConfigurationUpdated": "Серверска поставка је ажурирана", + "MessageNamedServerConfigurationUpdatedWithValue": "Одељак серверске поставке {0} је ажуриран", + "MessageApplicationUpdatedTo": "Џелифин сервер је ажуриран на {0}", + "MessageApplicationUpdated": "Џелифин сервер је ажуриран", + "Latest": "Последње", + "LabelRunningTimeValue": "Време рада: {0}", + "LabelIpAddressValue": "ИП адреса: {0}", + "ItemRemovedWithName": "{0} уклоњено из библиотеке", + "ItemAddedWithName": "{0} додато у библиотеку", + "Inherit": "Наследи", + "HomeVideos": "Кућни Видео", + "HeaderRecordingGroups": "Групе снимања", + "HeaderNextUp": "Следи", + "HeaderLiveTV": "ТВ уживо", + "HeaderFavoriteSongs": "Омиљене песме", + "HeaderFavoriteShows": "Омиљене серије", + "HeaderFavoriteEpisodes": "Омиљене епизоде", + "HeaderFavoriteArtists": "Омиљени извођачи", + "HeaderFavoriteAlbums": "Омиљени албуми", + "HeaderContinueWatching": "Настави гледање", + "HeaderAlbumArtists": "Извођачи албума", + "Genres": "Жанрови", + "Folders": "Фасцикле", + "Favorites": "Омиљено", + "FailedLoginAttemptWithUserName": "Неуспели покушај пријавe са {0}", + "DeviceOnlineWithName": "{0} је повезан", + "DeviceOfflineWithName": "{0} је прекинуо везу", + "Collections": "Колекције", + "ChapterNameValue": "Поглавље {0}", + "Channels": "Канали", + "CameraImageUploadedFrom": "Нова фотографија је учитана са {0}", + "Books": "Књиге", + "AuthenticationSucceededWithUserName": "{0} Успешна аутентификација", + "Artists": "Извођачи", + "Application": "Апликација", + "AppDeviceValues": "Апликација: {0}, Уређај: {1}", + "Albums": "Албуми", + "TaskDownloadMissingSubtitlesDescription": "Претражује интернет за недостајуће титлове на основу конфигурације метаподатака.", + "TaskDownloadMissingSubtitles": "Преузмите недостајуће титлове", + "TaskRefreshChannelsDescription": "Освежава информације о интернет каналу.", + "TaskRefreshChannels": "Освежи канале", + "TaskCleanTranscodeDescription": "Брише датотеке за кодирање старије од једног дана.", + "TaskCleanTranscode": "Очистите директоријум преноса", + "TaskUpdatePluginsDescription": "Преузима и инсталира исправке за додатке који су конфигурисани за аутоматско ажурирање.", + "TaskUpdatePlugins": "Ажурирајте додатке", + "TaskRefreshPeopleDescription": "Ажурира метаподатке за глумце и редитеље у вашој медијској библиотеци.", + "TaskRefreshPeople": "Освежите кориснике", + "TaskCleanLogsDescription": "Брише логове старије од {0} дана.", + "TaskCleanLogs": "Очистите директоријум логова", + "TaskRefreshLibraryDescription": "Скенира вашу медијску библиотеку за нове датотеке и освежава метаподатке.", + "TaskRefreshLibrary": "Скенирај библиотеку медија", + "TaskRefreshChapterImagesDescription": "Ствара сличице за видео записе који имају поглавља.", + "TaskRefreshChapterImages": "Издвоји слике из поглавља", + "TaskCleanCacheDescription": "Брише Кеш фајлове који више нису потребни систему.", + "TaskCleanCache": "Очистите Кеш Директоријум", + "TasksChannelsCategory": "Интернет канали", + "TasksApplicationCategory": "Апликација", + "TasksLibraryCategory": "Библиотека", + "TasksMaintenanceCategory": "Одржавање", + "TaskCleanActivityLogDescription": "Брише историју активности старију од конфигурисаног броја година.", + "TaskCleanActivityLog": "Очисти историју активности", + "Undefined": "Недефинисано", + "Forced": "Принудно", + "Default": "Подразумевано", + "TaskOptimizeDatabase": "Оптимизуј банку података", + "TaskOptimizeDatabaseDescription": "Сажима базу података и скраћује слободан простор. Покретање овог задатка након скенирања библиотеке или других промена које подразумевају измене базе података које могу побољшати перформансе.", + "External": "Спољно", + "TaskKeyframeExtractorDescription": "Екстрактује кључне сличице из видео датотека да би креирао више прецизнију HLS плејлисту. Овај задатак може да потраје дуже време.", + "TaskKeyframeExtractor": "Екстрактор кључних сличица", + "HearingImpaired": "ослабљен слух", + "TaskAudioNormalization": "Нормализација звука", + "TaskCleanCollectionsAndPlaylists": "Очистите колекције и плејлисте", + "TaskAudioNormalizationDescription": "Скенира датотеке за податке о нормализацији звука.", + "TaskRefreshTrickplayImages": "Направи сличице за визуелно премотавање", + "TaskRefreshTrickplayImagesDescription": "Прављење сличица које помажу код визуелног премотавања видео-снимака.", + "TaskDownloadMissingLyrics": "Преузми стихове који недостају", + "TaskCleanCollectionsAndPlaylistsDescription": "Уклања ставке које више не постоје из колекција и плејлиста.", + "TaskExtractMediaSegments": "Скенирај сегменте медија", + "TaskExtractMediaSegmentsDescription": "Извлачи или добавља сегменте медија у додацима који раде са MediaSegment-ом.", + "TaskMoveTrickplayImagesDescription": "Премешта постојеће сличице за визуелно премотавање сходно подешавањима библиотеке.", + "CleanupUserDataTask": "Задатак чишћења корисничких података", + "CleanupUserDataTaskDescription": "Чисти све корисничке податке (напредак гледања, ознаке за омиљено...) медија који нису доступни 90 дана или дуже.", + "TaskMoveTrickplayImages": "Промени локацију сличица за визуелно премотавање", + "TaskDownloadMissingLyricsDescription": "Преузми стихове песама" +} diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json new file mode 100644 index 00000000..23acd3c5 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -0,0 +1,141 @@ +{ + "Albums": "Album", + "AppDeviceValues": "Applikation: {0}, Enhet: {1}", + "Application": "Applikation", + "Artists": "Artister", + "AuthenticationSucceededWithUserName": "{0} har autentiserats", + "Books": "Böcker", + "CameraImageUploadedFrom": "En ny kamerabild har laddats upp från {0}", + "Channels": "Kanaler", + "ChapterNameValue": "Kapitel {0}", + "Collections": "Samlingar", + "DeviceOfflineWithName": "{0} har kopplat ned", + "DeviceOnlineWithName": "{0} är ansluten", + "FailedLoginAttemptWithUserName": "Misslyckat inloggningsförsök från {0}", + "Favorites": "Favoriter", + "Folders": "Mappar", + "Genres": "Genrer", + "HeaderAlbumArtists": "Albumartister", + "HeaderContinueWatching": "Fortsätt titta", + "HeaderFavoriteAlbums": "Favoritalbum", + "HeaderFavoriteArtists": "Favoritartister", + "HeaderFavoriteEpisodes": "Favoritavsnitt", + "HeaderFavoriteShows": "Favoritserier", + "HeaderFavoriteSongs": "Favoritlåtar", + "HeaderLiveTV": "Direktsänd TV", + "HeaderNextUp": "Nästa", + "HeaderRecordingGroups": "Inspelningsgrupper", + "HomeVideos": "Hemmavideor", + "Inherit": "Ärv", + "ItemAddedWithName": "{0} lades till i biblioteket", + "ItemRemovedWithName": "{0} togs bort från biblioteket", + "LabelIpAddressValue": "IP-adress: {0}", + "LabelRunningTimeValue": "Speltid: {0}", + "Latest": "Senaste", + "MessageApplicationUpdated": "Jellyfin Server har uppdaterats", + "MessageApplicationUpdatedTo": "Jellyfin Server har uppdaterats till {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Serverinställningarna {0} har uppdaterats", + "MessageServerConfigurationUpdated": "Serverkonfigurationen har uppdaterats", + "MixedContent": "Blandat innehåll", + "Movies": "Filmer", + "Music": "Musik", + "MusicVideos": "Musikvideor", + "NameInstallFailed": "{0} installationen misslyckades", + "NameSeasonNumber": "Säsong {0}", + "NameSeasonUnknown": "Okänd säsong", + "NewVersionIsAvailable": "En ny version av Jellyfin Server är tillgänglig för nedladdning.", + "NotificationOptionApplicationUpdateAvailable": "Ny programversion tillgänglig", + "NotificationOptionApplicationUpdateInstalled": "Programuppdatering installerad", + "NotificationOptionAudioPlayback": "Ljuduppspelning har påbörjats", + "NotificationOptionAudioPlaybackStopped": "Ljuduppspelning stoppades", + "NotificationOptionCameraImageUploaded": "Kamerabild har laddats upp", + "NotificationOptionInstallationFailed": "Installationen misslyckades", + "NotificationOptionNewLibraryContent": "Nytt innehåll har lagts till", + "NotificationOptionPluginError": "Fel uppstod med tillägget", + "NotificationOptionPluginInstalled": "Tillägg har installerats", + "NotificationOptionPluginUninstalled": "Tillägg har avinstallerats", + "NotificationOptionPluginUpdateInstalled": "Tillägg har uppdaterats", + "NotificationOptionServerRestartRequired": "Servern måste startas om", + "NotificationOptionTaskFailed": "Schemalagd aktivitet har misslyckats", + "NotificationOptionUserLockedOut": "Användare har låsts ut", + "NotificationOptionVideoPlayback": "Videouppspelning har påbörjats", + "NotificationOptionVideoPlaybackStopped": "Videouppspelning stoppades", + "Photos": "Bilder", + "Playlists": "Spellistor", + "Plugin": "Tillägg", + "PluginInstalledWithName": "{0} installerades", + "PluginUninstalledWithName": "{0} avinstallerades", + "PluginUpdatedWithName": "{0} uppdaterades", + "ProviderValue": "Leverantör: {0}", + "ScheduledTaskFailedWithName": "{0} misslyckades", + "ScheduledTaskStartedWithName": "{0} startades", + "ServerNameNeedsToBeRestarted": "{0} behöver startas om", + "Shows": "Serier", + "Songs": "Låtar", + "StartupEmbyServerIsLoading": "Jellyfin Server arbetar. Pröva igen snart.", + "SubtitleDownloadFailureFromForItem": "Undertexter kunde inte laddas ner från {0} till {1}", + "Sync": "Synk", + "System": "System", + "TvShows": "TV-serier", + "User": "Användare", + "UserCreatedWithName": "Användaren {0} har skapats", + "UserDeletedWithName": "Användaren {0} har tagits bort", + "UserDownloadingItemWithValues": "{0} laddar ner {1}", + "UserLockedOutWithName": "Användare {0} har utelåsts", + "UserOfflineFromDevice": "{0} har kopplat ned från {1}", + "UserOnlineFromDevice": "{0} är uppkopplad från {1}", + "UserPasswordChangedWithName": "Lösenordet för {0} har ändrats", + "UserPolicyUpdatedWithName": "Användarpolicyn har uppdaterats för {0}", + "UserStartedPlayingItemWithValues": "{0} spelar {1} på {2}", + "UserStoppedPlayingItemWithValues": "{0} har stoppat uppspelningen av {1} på {2}", + "ValueHasBeenAddedToLibrary": "{0} har lagts till i ditt mediebibliotek", + "ValueSpecialEpisodeName": "Specialavsnitt - {0}", + "VersionNumber": "Version {0}", + "TaskDownloadMissingSubtitlesDescription": "Söker på internet efter saknade undertexter baserat på metadata-konfiguration.", + "TaskDownloadMissingSubtitles": "Ladda ner saknade undertexter", + "TaskRefreshChannelsDescription": "Uppdaterar information för internetkanaler.", + "TaskRefreshChannels": "Uppdatera kanaler", + "TaskCleanTranscodeDescription": "Raderar omkodningsfiler äldre än en dag.", + "TaskCleanTranscode": "Rensa omkodningskatalog", + "TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till tillägg som är konfigurerade att uppdateras automatiskt.", + "TaskUpdatePlugins": "Uppdatera tillägg", + "TaskRefreshPeopleDescription": "Uppdaterar metadata för skådespelare och regissörer i ditt mediabibliotek.", + "TaskCleanLogsDescription": "Raderar loggfiler som är mer än {0} dagar gamla.", + "TaskCleanLogs": "Rensa loggkatalog", + "TaskRefreshLibraryDescription": "Skannar ditt mediabibliotek efter nya filer och uppdaterar metadata.", + "TaskRefreshLibrary": "Skanna mediabibliotek", + "TaskRefreshChapterImagesDescription": "Skapa miniatyrbilder för videor med kapitel.", + "TaskRefreshChapterImages": "Extrahera kapitelbilder", + "TaskCleanCacheDescription": "Radera cachade filer som systemet inte längre behöver.", + "TaskCleanCache": "Rensa cachekatalog", + "TasksChannelsCategory": "Internetkanaler", + "TasksApplicationCategory": "Applikation", + "TasksLibraryCategory": "Bibliotek", + "TasksMaintenanceCategory": "Underhåll", + "TaskRefreshPeople": "Uppdatera personer", + "TaskCleanActivityLogDescription": "Radera aktivitetslogginlägg äldre än konfigurerad ålder.", + "TaskCleanActivityLog": "Rensa aktivitetslogg", + "Undefined": "Odefinierad", + "Forced": "Tvingad", + "Default": "Standard", + "TaskOptimizeDatabase": "Optimera databasen", + "TaskOptimizeDatabaseDescription": "Komprimerar databasen och trunkerar ledigt utrymme. Prestandan kan förbättras genom att köra denna aktivitet efter att du har skannat biblioteket eller gjort andra förändringar som indikerar att databasen har modifierats.", + "TaskKeyframeExtractorDescription": "Exporterar nyckelbildrutor från videofiler för att skapa mer exakta HLS-spellistor. Denna körning kan ta lång tid.", + "TaskKeyframeExtractor": "Extraktor för nyckelbildrutor", + "External": "Extern", + "HearingImpaired": "Hörselskadad", + "TaskRefreshTrickplayImages": "Generera Trickplay-bilder", + "TaskRefreshTrickplayImagesDescription": "Skapar trickplay-förhandsvisningar för videor i aktiverade bibliotek.", + "TaskCleanCollectionsAndPlaylists": "Rensa upp samlingar och spellistor", + "TaskAudioNormalization": "Ljudnormalisering", + "TaskCleanCollectionsAndPlaylistsDescription": "Tar bort objekt från samlingar och spellistor som inte längre finns.", + "TaskAudioNormalizationDescription": "Skannar filer för ljudnormaliseringsdata.", + "TaskDownloadMissingLyrics": "Ladda ner saknad låttext", + "TaskDownloadMissingLyricsDescription": "Laddar ner låttexter", + "TaskExtractMediaSegments": "Skanning av mediesegment", + "TaskExtractMediaSegmentsDescription": "Extraherar eller hämtar ut mediesegmen från tillägg som stöder MediaSegment.", + "TaskMoveTrickplayImages": "Migrera platsen för Trickplay-bilder", + "TaskMoveTrickplayImagesDescription": "Flyttar befintliga trickplay-filer enligt bibliotekets inställningar.", + "CleanupUserDataTaskDescription": "Tar bort all användardata (såsom vad du sett, favoriter med mera) för media som inte funnits på enheten på minst 90 dagar.", + "CleanupUserDataTask": "Uppgift för rensning av användardata" +} diff --git a/Emby.Server.Implementations/Localization/Core/sw.json b/Emby.Server.Implementations/Localization/Core/sw.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/sw.json @@ -0,0 +1 @@ +{} diff --git a/Emby.Server.Implementations/Localization/Core/ta.json b/Emby.Server.Implementations/Localization/Core/ta.json new file mode 100644 index 00000000..defdc592 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ta.json @@ -0,0 +1,141 @@ +{ + "VersionNumber": "பதிப்பு {0}", + "ValueSpecialEpisodeName": "சிறப்பு - {0}", + "TasksMaintenanceCategory": "பராமரிப்பு", + "TaskCleanCache": "தற்காலிக சேமிப்பு கோப்பகத்தை சுத்தம் செய்யவும்", + "TaskRefreshChapterImages": "அத்தியாயப் படங்களை பிரித்தெடுக்கவும்", + "TaskRefreshPeople": "மக்களைப் புதுப்பிக்கவும்", + "TaskCleanTranscode": "டிரான்ஸ்கோட் கோப்பகத்தை சுத்தம் செய்யவும்", + "TaskRefreshChannelsDescription": "இணையச் சேனல் தகவல்களைப் புதுப்பிக்கிறது.", + "System": "ஒருங்கியம்", + "NotificationOptionTaskFailed": "திட்டமிடப்பட்ட பணி தோல்வியடைந்தது", + "NotificationOptionPluginUpdateInstalled": "உட்செருகி புதுப்பிக்கப்பட்டது", + "NotificationOptionPluginUninstalled": "உட்செருகி நீக்கப்பட்டது", + "NotificationOptionPluginInstalled": "உட்செருகி நிறுவப்பட்டது", + "NotificationOptionPluginError": "உட்செருகி செயலிழந்தது", + "NotificationOptionCameraImageUploaded": "புகைப்படம் பதிவேற்றப்பட்டது", + "MixedContent": "கலப்பு உள்ளடக்கங்கள்", + "MessageServerConfigurationUpdated": "சேவையக அமைப்புகள் புதுப்பிக்கப்பட்டன", + "MessageApplicationUpdatedTo": "ஜெல்லிஃபின் சேவையகம் {0} இற்கு புதுப்பிக்கப்பட்டது", + "MessageApplicationUpdated": "ஜெல்லிஃபின் சேவையகம் புதுப்பிக்கப்பட்டது", + "Inherit": "மரபுரிமையாகப் பெறு", + "HeaderRecordingGroups": "பதிவு குழுக்கள்", + "Folders": "கோப்புறைகள்", + "FailedLoginAttemptWithUserName": "{0} இலிருந்து உள்நுழைவு முயற்சி தோல்வியடைந்தது", + "DeviceOnlineWithName": "{0} இணைக்கப்பட்டது", + "DeviceOfflineWithName": "{0} துண்டிக்கப்பட்டது", + "Collections": "தொகுப்புகள்", + "CameraImageUploadedFrom": "{0} இல் இருந்து புதிய புகைப்படம் பதிவேற்றப்பட்டது", + "AppDeviceValues": "செயலி: {0}, சாதனம்: {1}", + "TaskDownloadMissingSubtitles": "விடுபட்டுபோன வசன வரிகளைப் பதிவிறக்கு", + "TaskRefreshChannels": "சேனல்களை புதுப்பி", + "TaskUpdatePlugins": "உட்செருகிகளை புதுப்பி", + "TaskRefreshLibrary": "ஊடக நூலகத்தை ஆராய்", + "TasksChannelsCategory": "இணைய சேனல்கள்", + "TasksApplicationCategory": "செயலி", + "TasksLibraryCategory": "நூலகம்", + "UserPolicyUpdatedWithName": "பயனர் கொள்கை {0} இற்கு புதுப்பிக்கப்பட்டுள்ளது", + "UserPasswordChangedWithName": "{0} பயனருக்கு கடவுச்சொல் மாற்றப்பட்டுள்ளது", + "UserLockedOutWithName": "பயனர் {0} முடக்கப்பட்டார்", + "UserDownloadingItemWithValues": "{0} ஆல் {1} பதிவிறக்கப்படுகிறது", + "UserDeletedWithName": "பயனர் {0} நீக்கப்பட்டார்", + "UserCreatedWithName": "பயனர் {0} உருவாக்கப்பட்டார்", + "User": "பயனர்", + "TvShows": "தொலைக்காட்சித் தொடர்கள்", + "Sync": "ஒத்திசைவு", + "StartupEmbyServerIsLoading": "ஜெல்லிஃபின் சேவையகம் துவங்குகிறது. சிறிது நேரம் கழித்து முயற்சிக்கவும்.", + "Songs": "பாடல்கள்", + "Shows": "நிகழ்ச்சிகள்", + "ServerNameNeedsToBeRestarted": "{0} மறுதொடக்கம் செய்யப்பட வேண்டும்", + "ScheduledTaskStartedWithName": "{0} துவங்கியது", + "ScheduledTaskFailedWithName": "{0} தோல்வியடைந்தது", + "ProviderValue": "வழங்குநர்: {0}", + "PluginUpdatedWithName": "{0} புதுப்பிக்கப்பட்டது", + "PluginUninstalledWithName": "{0} நீக்கப்பட்டது", + "PluginInstalledWithName": "{0} நிறுவப்பட்டது", + "Plugin": "உட்செருகி", + "Playlists": "தொடர் பட்டியல்கள்", + "Photos": "புகைப்படங்கள்", + "NotificationOptionVideoPlaybackStopped": "நிகழ்பட ஒளிபரப்பு நிறுத்தப்பட்டது", + "NotificationOptionVideoPlayback": "நிகழ்பட ஒளிபரப்பு துவங்கியது", + "NotificationOptionUserLockedOut": "பயனர் கணக்கு முடக்கப்பட்டது", + "NotificationOptionServerRestartRequired": "சேவையக மறுதொடக்கம் தேவை", + "NotificationOptionNewLibraryContent": "புதிய உள்ளடக்கங்கள் சேர்க்கப்பட்டன", + "NotificationOptionInstallationFailed": "நிறுவல் தோல்வியடைந்தது", + "NotificationOptionAudioPlaybackStopped": "ஒலி இசைத்தல் நிறுத்தப்பட்டது", + "NotificationOptionAudioPlayback": "ஒலி இசைக்கத் துவங்கியுள்ளது", + "NotificationOptionApplicationUpdateInstalled": "செயலி புதுப்பிக்கப்பட்டது", + "NotificationOptionApplicationUpdateAvailable": "செயலியினை புதுப்பிக்கலாம்", + "NameSeasonUnknown": "அறியப்படாத பருவம்", + "NameSeasonNumber": "பருவம் {0}", + "NameInstallFailed": "{0} நிறுவல் தோல்வியடைந்தது", + "MusicVideos": "இசை கானொளி", + "Music": "இசை", + "Movies": "திரைப்படங்கள்", + "Latest": "புதியவை", + "LabelRunningTimeValue": "ஓடும் நேரம்: {0}", + "LabelIpAddressValue": "ஐபி முகவரி: {0}", + "ItemRemovedWithName": "{0} நூலகத்திலிருந்து அகற்றப்பட்டது", + "ItemAddedWithName": "{0} நூலகத்தில் சேர்க்கப்பட்டது", + "HeaderNextUp": "அடுத்தது", + "HeaderLiveTV": "நேரடித் தொலைக்காட்சி", + "HeaderFavoriteSongs": "பிடித்த பாடல்கள்", + "HeaderFavoriteShows": "பிடித்த தொடர்கள்", + "HeaderFavoriteEpisodes": "பிடித்த அத்தியாயங்கள்", + "HeaderFavoriteArtists": "பிடித்த கலைஞர்கள்", + "HeaderFavoriteAlbums": "பிடித்த ஆல்பங்கள்", + "HeaderContinueWatching": "தொடர்ந்து பார்", + "HeaderAlbumArtists": "கலைஞரின் ஆல்பம்", + "Genres": "வகைகள்", + "Favorites": "பிடித்தவை", + "ChapterNameValue": "அத்தியாயம் {0}", + "Channels": "சேனல்கள்", + "Books": "புத்தகங்கள்", + "AuthenticationSucceededWithUserName": "{0} வெற்றிகரமாக அங்கீகரிக்கப்பட்டது", + "Artists": "கலைஞர்கள்", + "Application": "செயலி", + "Albums": "ஆல்பங்கள்", + "NewVersionIsAvailable": "ஜெல்லிஃபின் சேவையகத்தின் புதிய பதிப்பு பதிவிறக்கத்திற்கு கிடைக்கிறது.", + "MessageNamedServerConfigurationUpdatedWithValue": "சேவையக உள்ளமைவு பிரிவு {0} புதுப்பிக்கப்பட்டது", + "TaskCleanCacheDescription": "கணினிக்கு இனி தேவைப்படாத தற்காலிக கோப்புகளை நீக்கு.", + "UserOfflineFromDevice": "{0} இலிருந்து {1} துண்டிக்கப்பட்டுள்ளது", + "SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0} இல் இருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன", + "TaskDownloadMissingSubtitlesDescription": "மீத்தரவு உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.", + "TaskCleanTranscodeDescription": "ஒரு நாளைக்கு மேற்பட்ட பழைய டிரான்ஸ்கோட் கோப்புகளை நீக்குகிறது.", + "TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்கும்படி கட்டமைக்கப்பட்ட உட்செருகிகளுக்கான புதுப்பிப்புகளை பதிவிறக்கி நிறுவுகிறது.", + "TaskRefreshPeopleDescription": "உங்கள் ஊடக நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மீத்தரவை புதுப்பிக்கும்.", + "TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.", + "TaskCleanLogs": "பதிவு அடைவை சுத்தம் செய்யுங்கள்", + "TaskRefreshLibraryDescription": "புதிய கோப்புகளுக்காக உங்கள் ஊடக நூலகத்தை ஆராய்ந்து மீத்தரவை புதுப்பிக்கும்.", + "TaskRefreshChapterImagesDescription": "அத்தியாயங்களைக் கொண்ட வீடியோக்களுக்கான சிறு உருவங்களை உருவாக்குகிறது.", + "ValueHasBeenAddedToLibrary": "உங்கள் மீடியா நூலகத்தில் {0} சேர்க்கப்பட்டது", + "UserOnlineFromDevice": "{1} இருந்து {0} ஆன்லைன்", + "HomeVideos": "முகப்பு வீடியோக்கள்", + "UserStoppedPlayingItemWithValues": "{0} {2} இல் {1} முடித்துவிட்டது", + "UserStartedPlayingItemWithValues": "{0} {2}இல் {1} ஐ இயக்குகிறது", + "TaskCleanActivityLogDescription": "உள்ளமைக்கப்பட்ட வயதை விட பழைய செயல்பாட்டு பதிவு உள்ளீடுகளை நீக்குகிறது.", + "TaskCleanActivityLog": "செயல்பாட்டு பதிவை அழி", + "Undefined": "வரையறுக்கப்படாத", + "Forced": "கட்டாயப்படுத்தப்பட்டது", + "Default": "இயல்புநிலை", + "TaskOptimizeDatabaseDescription": "தரவுத்தளத்தை சுருக்கி, இலவச இடத்தை குறைக்கிறது. நூலகத்தை ஸ்கேன் செய்தபின் அல்லது தரவுத்தள மாற்றங்களைக் குறிக்கும் பிற மாற்றங்களைச் செய்தபின் இந்த பணியை இயக்குவது செயல்திறனை மேம்படுத்தக்கூடும்.", + "TaskOptimizeDatabase": "தரவுத்தளத்தை மேம்படுத்தவும்", + "TaskKeyframeExtractorDescription": "மிகவும் துல்லியமான HLS பிளேலிஸ்ட்களை உருவாக்க வீடியோ கோப்புகளிலிருந்து கீஃப்ரேம்களைப் பிரித்தெடுக்கிறது. இந்த பணி நீண்ட காலமாக இருக்கலாம்.", + "TaskKeyframeExtractor": "கீஃப்ரேம் எக்ஸ்ட்ராக்டர்", + "External": "வெளி", + "HearingImpaired": "செவித்திறன் குறைபாடுடையவர்", + "TaskRefreshTrickplayImages": "முன்னோட்ட படங்களை உருவாக்கு", + "TaskRefreshTrickplayImagesDescription": "செயல்பாட்டில் உள்ள தொகுப்புகளுக்கு முன்னோட்ட படங்களை உருவாக்கும்.", + "TaskCleanCollectionsAndPlaylists": "சேகரிப்புகள் மற்றும் பிளேலிஸ்ட்களை சுத்தம் செய்யவும்", + "TaskCleanCollectionsAndPlaylistsDescription": "சேகரிப்புகள் மற்றும் பிளேலிஸ்ட்களில் இருந்து உருப்படிகளை நீக்குகிறது.", + "TaskAudioNormalization": "ஆடியோ இயல்பாக்கம்", + "TaskAudioNormalizationDescription": "ஆடியோ இயல்பாக்குதல் தரவுக்காக கோப்புகளை ஸ்கேன் செய்கிறது.", + "TaskDownloadMissingLyrics": "விடுபட்ட பாடல் வரிகளைப் பதிவிறக்கவும்", + "TaskDownloadMissingLyricsDescription": "பாடல்களுக்கான வரிகளைப் பதிவிறக்குகிறது", + "TaskMoveTrickplayImages": "ட்ரிக்பிளே பட இருப்பிடத்தை நகர்த்து", + "TaskMoveTrickplayImagesDescription": "நூலக அமைப்புகளுக்கு ஏற்ப ஏற்கனவே உள்ள ட்ரிக்பிளே கோப்புகளை நகர்த்துகிறது.", + "TaskExtractMediaSegments": "மீடியா பிரிவு ஸ்கேன்", + "TaskExtractMediaSegmentsDescription": "மீடியாசெக்மென்ட் இயக்கப்பட்ட செருகுநிரல்களிலிருந்து மீடியா பிரிவுகளைப் பிரித்தெடுக்கிறது அல்லது பெறுகிறது.", + "CleanupUserDataTaskDescription": "குறைந்தது 90 நாட்களுக்கு இல்லாத மீடியாவிலிருந்து அனைத்து பயனர் தரவையும் (கண்காணிப்பு நிலை, பிடித்த நிலை போன்றவை) சுத்தம் செய்கிறது.", + "CleanupUserDataTask": "பயனர் தரவை சுத்தம் செய்யும் பணி" +} diff --git a/Emby.Server.Implementations/Localization/Core/te.json b/Emby.Server.Implementations/Localization/Core/te.json new file mode 100644 index 00000000..ca9e3452 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/te.json @@ -0,0 +1,64 @@ +{ + "ValueSpecialEpisodeName": "ప్రత్యేక - {0}", + "Sync": "సమకాలీకరించు", + "Songs": "పాటలు", + "Shows": "ప్రదర్శనలు", + "Playlists": "ప్లేజాబితాలు", + "Photos": "ఫోటోలు", + "MusicVideos": "మ్యూజిక్ వీడియోలు", + "Music": "సంగీతం", + "Movies": "సినిమాలు", + "HeaderContinueWatching": "చూడటం కొనసాగించండి", + "HeaderAlbumArtists": "ఆల్బమ్ కళాకారులు", + "Genres": "శైలులు", + "Forced": "బలవంతంగా", + "Folders": "ఫోల్డర్లు", + "Favorites": "ఇష్టమైనవి", + "Default": "డిఫాల్ట్", + "Collections": "సేకరణలు", + "Channels": "ఛానెల్‌లు", + "Books": "పుస్తకాలు", + "Artists": "కళాకారులు", + "Albums": "ఆల్బమ్‌లు", + "HearingImpaired": "వినికిడి లోపం", + "HomeVideos": "హోమ్ వీడియోలు", + "AppDeviceValues": "అప్లికేషన్ : {0}, పరికరం: {1}", + "Application": "అప్లికేషన్", + "AuthenticationSucceededWithUserName": "విజయవంతంగా ఆమోదించబడింది", + "CameraImageUploadedFrom": "{0} నుండి కొత్త కెమెరా చిత్రం అప్‌లోడ్ చేయబడింది", + "ChapterNameValue": "అధ్యాయం", + "DeviceOfflineWithName": "{0} డిస్‌కనెక్ట్ చేయబడింది", + "DeviceOnlineWithName": "{0} కనెక్ట్ చేయబడింది", + "External": "బాహ్య", + "FailedLoginAttemptWithUserName": "{0} నుండి విఫలమైన లాగిన్ ప్రయత్నం", + "HeaderFavoriteAlbums": "ఇష్టమైన ఆల్బమ్‌లు", + "HeaderFavoriteArtists": "ఇష్టమైన కళాకారులు", + "HeaderFavoriteEpisodes": "ఇష్టమైన ఎపిసోడ్‌లు", + "HeaderFavoriteShows": "ఇష్టమైన ప్రదర్శనలు", + "HeaderFavoriteSongs": "ఇష్టమైన పాటలు", + "HeaderLiveTV": "ప్రత్యక్ష TV", + "HeaderNextUp": "తదుపరి", + "HeaderRecordingGroups": "రికార్డింగ్ గుంపులు", + "MessageApplicationUpdated": "జెల్లీఫిన్ సర్వర్ అప్‌డేట్ చేయడం పూర్తి అయ్యింది", + "MessageApplicationUpdatedTo": "జెల్లీఫిన్ సర్వర్ {0} వెర్షన్ కి అప్‌డేట్ చెయ్యబడింది", + "MessageServerConfigurationUpdated": "సర్వర్ కన్ఫిగరేషన్ అప్డేట్ చేయబడింది", + "NewVersionIsAvailable": "జెల్లీఫిన్ సర్వర్ యొక్క కొత్త వెర్షన్ డౌన్‌లోడ్ చేసుకోవడానికి అందుబాటులో ఉంది.", + "NotificationOptionApplicationUpdateInstalled": "అప్లికేషన్ అప్‌డేట్ ఇన్‌స్టాల్ చేయబడింది", + "ItemAddedWithName": "{0} లైబ్రరీకి జోడించబడింది", + "ItemRemovedWithName": "లైబ్రరీ నుండి {0} తీసివేయబడింది", + "LabelIpAddressValue": "ఐపీ చిరునామా: {0}", + "LabelRunningTimeValue": "నడుస్తున్న సమయం: {0}", + "Latest": "తాజా", + "NameInstallFailed": "{0} ఇన్‌స్టాలేషన్ విఫలమైంది", + "NameSeasonUnknown": "భాగం తెలియదు", + "NotificationOptionApplicationUpdateAvailable": "అప్లికేషన్ అప్‌డేట్ అందుబాటులో ఉంది", + "NameSeasonNumber": "సీజన్ {0}", + "NotificationOptionAudioPlaybackStopped": "ఆడియో ఆడటం ఆగిపోయింది", + "NotificationOptionNewLibraryContent": "కొత్త కంటెంట్ జోడించబడింది", + "MixedContent": "వివిధ రకాల కంటెంట్", + "NotificationOptionAudioPlayback": "ఆడియో ప్లే కావడం మొదలైంది", + "NotificationOptionCameraImageUploaded": "కెమెరా చిత్రాన్ని అప్లోడ్ చేశారు", + "NotificationOptionInstallationFailed": "ఇన్స్టాలేషన్ విఫలమైంది", + "NotificationOptionServerRestartRequired": "సర్వర్ రీస్టార్ట్ అవసరం", + "Inherit": "సంక్రమించు" +} diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json new file mode 100644 index 00000000..65ddb55e --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/th.json @@ -0,0 +1,141 @@ +{ + "ProviderValue": "ผู้ให้บริการ: {0}", + "PluginUpdatedWithName": "อัปเดต {0} แล้ว", + "PluginUninstalledWithName": "ถอนการติดตั้ง {0} แล้ว", + "PluginInstalledWithName": "ติดตั้ง {0} แล้ว", + "Plugin": "ปลั๊กอิน", + "Playlists": "เพลย์ลิสต์", + "Photos": "รูปภาพ", + "NotificationOptionVideoPlaybackStopped": "หยุดเล่นวิดีโอ", + "NotificationOptionVideoPlayback": "เริ่มเล่นวิดีโอ", + "NotificationOptionUserLockedOut": "ผู้ใช้ถูกล็อก", + "NotificationOptionTaskFailed": "งานตามกำหนดการล้มเหลว", + "NotificationOptionServerRestartRequired": "จำเป็นต้องรีสตาร์ทเซิร์ฟเวอร์", + "NotificationOptionPluginUpdateInstalled": "ติดตั้งการอัปเดตปลั๊กอินแล้ว", + "NotificationOptionPluginUninstalled": "ถอนการติดตั้งปลั๊กอินแล้ว", + "NotificationOptionPluginInstalled": "ติดตั้งปลั๊กอินแล้ว", + "NotificationOptionPluginError": "ปลั๊กอินล้มเหลว", + "NotificationOptionNewLibraryContent": "เพิ่มเนื้อหาใหม่แล้ว", + "NotificationOptionInstallationFailed": "การติดตั้งล้มเหลว", + "NotificationOptionCameraImageUploaded": "อัปโหลดภาพถ่ายแล้ว", + "NotificationOptionAudioPlaybackStopped": "หยุดเล่นเสียง", + "NotificationOptionAudioPlayback": "เริ่มเล่นเสียง", + "NotificationOptionApplicationUpdateInstalled": "ติดตั้งการอัปเดตแอปพลิเคชันแล้ว", + "NotificationOptionApplicationUpdateAvailable": "มีการอัปเดตแอปพลิเคชัน", + "NewVersionIsAvailable": "เวอร์ชันใหม่ของเซิร์ฟเวอร์ Jellyfin พร้อมให้ดาวน์โหลดแล้ว", + "NameSeasonUnknown": "ไม่ทราบซีซัน", + "NameSeasonNumber": "ซีซัน {0}", + "NameInstallFailed": "การติดตั้ง {0} ล้มเหลว", + "MusicVideos": "มิวสิควิดีโอ", + "Music": "ดนตรี", + "Movies": "ภาพยนตร์", + "MixedContent": "เนื้อหาผสม", + "MessageServerConfigurationUpdated": "อัปเดตการกำหนดค่าเซิร์ฟเวอร์แล้ว", + "MessageNamedServerConfigurationUpdatedWithValue": "อัปเดตการกำหนดค่าเซิร์ฟเวอร์ในส่วน {0} แล้ว", + "MessageApplicationUpdatedTo": "เซิร์ฟเวอร์ Jellyfin ได้รับการอัปเดตเป็น {0}", + "MessageApplicationUpdated": "อัพเดตเซิร์ฟเวอร์ Jellyfin แล้ว", + "Latest": "ล่าสุด", + "LabelRunningTimeValue": "ผ่านไปแล้ว: {0}", + "LabelIpAddressValue": "ที่อยู่ IP: {0}", + "ItemRemovedWithName": "{0} ถูกลบออกจากไลบรารี", + "ItemAddedWithName": "{0} ถูกเพิ่มลงในไลบรารีแล้ว", + "Inherit": "สืบทอด", + "HomeVideos": "โฮมวิดีโอ", + "HeaderRecordingGroups": "กลุ่มการบันทึก", + "HeaderNextUp": "ถัดไป", + "HeaderLiveTV": "ทีวีสด", + "HeaderFavoriteSongs": "เพลงที่ชื่นชอบ", + "HeaderFavoriteShows": "รายการที่ชื่นชอบ", + "HeaderFavoriteEpisodes": "ตอนที่ชื่นชอบ", + "HeaderFavoriteArtists": "ศิลปินที่ชื่นชอบ", + "HeaderFavoriteAlbums": "อัมบั้มที่ชื่นชอบ", + "HeaderContinueWatching": "ดูต่อ", + "HeaderAlbumArtists": "ศิลปินอัลบั้ม", + "Genres": "ประเภท", + "Folders": "โฟลเดอร์", + "Favorites": "รายการโปรด", + "FailedLoginAttemptWithUserName": "ความพยายามในการเข้าสู่ระบบล้มเหลวจาก {0}", + "DeviceOnlineWithName": "{0} เชื่อมต่อสำเร็จแล้ว", + "DeviceOfflineWithName": "{0} ยกเลิกการเชื่อมต่อแล้ว", + "Collections": "คอลเลกชัน", + "ChapterNameValue": "บทที่ {0}", + "Channels": "ช่อง", + "CameraImageUploadedFrom": "ภาพถ่ายใหม่ได้ถูกอัปโหลดมาจาก {0}", + "Books": "หนังสือ", + "AuthenticationSucceededWithUserName": "{0} ยืนยันตัวตนสำเร็จแล้ว", + "Artists": "ศิลปิน", + "Application": "แอปพลิเคชัน", + "AppDeviceValues": "แอป: {0}, อุปกรณ์: {1}", + "Albums": "อัลบั้ม", + "ScheduledTaskStartedWithName": "{0} เริ่มต้น", + "ScheduledTaskFailedWithName": "{0} ล้มเหลว", + "Songs": "เพลง", + "Shows": "รายการ", + "ServerNameNeedsToBeRestarted": "{0} ต้องการการรีสตาร์ท", + "TaskDownloadMissingSubtitlesDescription": "ค้นหาคำบรรยายที่หายไปในอินเทอร์เน็ตตามค่ากำหนดในข้อมูลเมตา", + "TaskDownloadMissingSubtitles": "ดาวน์โหลดคำบรรยายที่ขาดหายไป", + "TaskRefreshChannelsDescription": "รีเฟรชข้อมูลช่องอินเทอร์เน็ต", + "TaskRefreshChannels": "รีเฟรชช่อง", + "TaskCleanTranscodeDescription": "ลบไฟล์ทรานส์โค้ดที่มีอายุมากกว่าหนึ่งวัน", + "TaskCleanTranscode": "ล้างไดเรกทอรีทรานส์โค้ด", + "TaskUpdatePluginsDescription": "ดาวน์โหลดและติดตั้งโปรแกรมปรับปรุงให้กับปลั๊กอินที่กำหนดค่าให้อัปเดตโดยอัตโนมัติ", + "TaskUpdatePlugins": "อัปเดตปลั๊กอิน", + "TaskRefreshPeopleDescription": "อัปเดตข้อมูลเมตานักแสดงและผู้กำกับในไลบรารีสื่อ", + "TaskRefreshPeople": "รีเฟรชบุคคล", + "TaskCleanLogsDescription": "ลบไฟล์บันทึกที่เก่ากว่า {0} วัน", + "TaskCleanLogs": "ล้างไดเรกทอรีบันทึก", + "TaskRefreshLibraryDescription": "สแกนไลบรารีสื่อของคุณเพื่อหาไฟล์ใหม่และรีเฟรชข้อมูลเมตา", + "TaskRefreshLibrary": "สแกนไลบรารีสื่อ", + "TaskRefreshChapterImagesDescription": "สร้างภาพขนาดย่อสำหรับวิดีโอที่มีบท", + "TaskRefreshChapterImages": "แตกรูปภาพบท", + "TaskCleanCacheDescription": "ลบไฟล์แคชที่ระบบไม่ต้องการ", + "TaskCleanCache": "ล้างไดเรกทอรีแคช", + "TasksChannelsCategory": "ช่องอินเทอร์เน็ต", + "TasksApplicationCategory": "แอปพลิเคชัน", + "TasksLibraryCategory": "ไลบรารี", + "TasksMaintenanceCategory": "ปิดซ่อมบำรุง", + "VersionNumber": "เวอร์ชัน {0}", + "ValueSpecialEpisodeName": "พิเศษ - {0}", + "ValueHasBeenAddedToLibrary": "เพิ่ม {0} ลงในไลบรารีสื่อของคุณแล้ว", + "UserStoppedPlayingItemWithValues": "{0} เล่นเสร็จแล้ว {1} บน {2}", + "UserStartedPlayingItemWithValues": "{0} กำลังเล่น {1} บน {2}", + "UserPolicyUpdatedWithName": "มีการอัปเดตนโยบายผู้ใช้ของ {0}", + "UserPasswordChangedWithName": "มีการเปลี่ยนรหัสผ่านของผู้ใช้ {0}", + "UserOnlineFromDevice": "{0} ออนไลน์จาก {1}", + "UserOfflineFromDevice": "{0} ได้ยกเลิกการเชื่อมต่อจาก {1}", + "UserLockedOutWithName": "ผู้ใช้ {0} ถูกล็อก", + "UserDownloadingItemWithValues": "{0} กำลังดาวน์โหลด {1}", + "UserDeletedWithName": "ลบผู้ใช้ {0} แล้ว", + "UserCreatedWithName": "สร้างผู้ใช้ {0} แล้ว", + "User": "ผู้ใช้งาน", + "TvShows": "รายการทีวี", + "System": "ระบบ", + "Sync": "ซิงค์", + "SubtitleDownloadFailureFromForItem": "ไม่สามารถดาวน์โหลดคำบรรยายจาก {0} สำหรับ {1} ได้", + "StartupEmbyServerIsLoading": "กำลังโหลดเซิร์ฟเวอร์ Jellyfin โปรดลองอีกครั้งในอีกสักครู่", + "Default": "ค่าเริ่มต้น", + "TaskCleanActivityLogDescription": "ลบบันทึกกิจกรรมที่เก่ากว่าค่าที่กำหนดไว้", + "TaskCleanActivityLog": "ล้างบันทึกกิจกรรม", + "Undefined": "ไม่ได้กำหนด", + "Forced": "บังคับใช้", + "TaskOptimizeDatabase": "ปรับปรุงประสิทธิภาพฐานข้อมูล", + "TaskOptimizeDatabaseDescription": "ลดขนาดการจัดเก็บฐานข้อมูล ใช้งานคำสั่งนี้หลังจากสแกนไลบรารีหรือหลังจากการเปลี่ยนแปลงฐานข้อมูล อาจจะทำให้ระบบทำงานเร็วขึ้น", + "External": "ภายนอก", + "HearingImpaired": "บกพร่องทางการได้ยิน", + "TaskKeyframeExtractor": "ตัวแยกคีย์เฟรม", + "TaskKeyframeExtractorDescription": "แยกคีย์เฟรมจากไฟล์วีดีโอเพื่อสร้างรายการ HLS ให้ถูกต้อง. กระบวนการนี้อาจใช้ระยะเวลานาน", + "TaskRefreshTrickplayImages": "สร้างไฟล์รูปภาพสำหรับ Trickplay", + "TaskRefreshTrickplayImagesDescription": "สร้างภาพตัวอย่างของวีดีโอในคลังที่เปิดใช้งาน Trickplay", + "TaskDownloadMissingLyrics": "ดาวน์โหลดเนื้อเพลงที่หายไป", + "TaskDownloadMissingLyricsDescription": "ดาวน์โหลดเนื้อเพลงสำหรับเพลง", + "TaskAudioNormalization": "ปรับระดับเสียงให้สม่ำเสมอ", + "TaskAudioNormalizationDescription": "สแกนไฟล์เพื่อค้นหาข้อมูลการปรับระดับเสียงให้สม่ำเสมอ", + "TaskCleanCollectionsAndPlaylists": "จัดระเบียบคอลเลกชันและเพลย์ลิสต์", + "TaskCleanCollectionsAndPlaylistsDescription": "ลบรายการออกจากคอลเลกชันและเพลย์ลิสต์ที่ไม่มีแล้ว", + "TaskExtractMediaSegments": "การสแกนส่วนของสื่อมีเดีย", + "TaskMoveTrickplayImagesDescription": "ย้ายไฟล์ Trickplay ตามการตั้งค่าของไลบรารี", + "TaskExtractMediaSegmentsDescription": "แยกหรือดึงส่วนของสื่อจากปลั๊กอินที่เปิดใช้งาน MediaSegment", + "TaskMoveTrickplayImages": "ย้ายตำแหน่งเก็บภาพตัวอย่าง Trickplay", + "CleanupUserDataTask": "ส่วนงานล้างข้อมูลผู้ใช้", + "CleanupUserDataTaskDescription": "ล้างข้อมูลผู้ใช้ทั้งหมด (สถานะการรับชม สถานะรายการโปรด ฯลฯ) จากสื่อที่ไม่ได้ใช้งานแล้วอย่างน้อย 90 วัน" +} diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json new file mode 100644 index 00000000..d13f662e --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -0,0 +1,141 @@ +{ + "Albums": "Albümler", + "AppDeviceValues": "Uygulama: {0}, Aygıt: {1}", + "Application": "Uygulama", + "Artists": "Sanatçılar", + "AuthenticationSucceededWithUserName": "{0} kimliği başarıyla doğrulandı", + "Books": "Kitaplar", + "CameraImageUploadedFrom": "{0} 'den yeni bir kamera resmi yüklendi", + "Channels": "Kanallar", + "ChapterNameValue": "{0}. Bölüm", + "Collections": "Koleksiyonlar", + "DeviceOfflineWithName": "{0} bağlantısı kesildi", + "DeviceOnlineWithName": "{0} bağlı", + "FailedLoginAttemptWithUserName": "{0} kullanıcısının başarısız oturum açma girişimi", + "Favorites": "Favoriler", + "Folders": "Klasörler", + "Genres": "Türler", + "HeaderAlbumArtists": "Albüm sanatçıları", + "HeaderContinueWatching": "İzlemeye Devam Et", + "HeaderFavoriteAlbums": "Favori Albümler", + "HeaderFavoriteArtists": "Favori Sanatçılar", + "HeaderFavoriteEpisodes": "Favori Bölümler", + "HeaderFavoriteShows": "Favori Diziler", + "HeaderFavoriteSongs": "Favori Şarkılar", + "HeaderLiveTV": "Canlı TV", + "HeaderNextUp": "Sıradaki Bölümler", + "HeaderRecordingGroups": "Kayıt Grupları", + "HomeVideos": "Ana Ekran Videoları", + "Inherit": "Devral", + "ItemAddedWithName": "{0} kütüphaneye eklendi", + "ItemRemovedWithName": "{0} kütüphaneden silindi", + "LabelIpAddressValue": "IP adresi: {0}", + "LabelRunningTimeValue": "Oynatma süresi: {0}", + "Latest": "En son", + "MessageApplicationUpdated": "Jellyfin Sunucusu güncellendi", + "MessageApplicationUpdatedTo": "Jellyfin Sunucusu {0} sürümüne güncellendi", + "MessageNamedServerConfigurationUpdatedWithValue": "Sunucu yapılandırma bölümü {0} güncellendi", + "MessageServerConfigurationUpdated": "Sunucu yapılandırması güncellendi", + "MixedContent": "Karışık içerik", + "Movies": "Filmler", + "Music": "Müzik", + "MusicVideos": "Müzik Videoları", + "NameInstallFailed": "{0} kurulumu başarısız", + "NameSeasonNumber": "{0}. Sezon", + "NameSeasonUnknown": "Sezon Bilinmiyor", + "NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir sürümü indirmek için hazır.", + "NotificationOptionApplicationUpdateAvailable": "Uygulama güncellemesi mevcut", + "NotificationOptionApplicationUpdateInstalled": "Uygulama güncellemesi yüklendi", + "NotificationOptionAudioPlayback": "Ses çalma başladı", + "NotificationOptionAudioPlaybackStopped": "Ses çalma durduruldu", + "NotificationOptionCameraImageUploaded": "Kamera fotoğrafı yüklendi", + "NotificationOptionInstallationFailed": "Kurulum hatası", + "NotificationOptionNewLibraryContent": "Yeni içerik eklendi", + "NotificationOptionPluginError": "Eklenti hatası", + "NotificationOptionPluginInstalled": "Eklenti yüklendi", + "NotificationOptionPluginUninstalled": "Eklenti kaldırıldı", + "NotificationOptionPluginUpdateInstalled": "Eklenti güncellemesi yüklendi", + "NotificationOptionServerRestartRequired": "Sunucunun yeniden başlatılması gerekiyor", + "NotificationOptionTaskFailed": "Zamanlanmış görev hatası", + "NotificationOptionUserLockedOut": "Kullanıcı hesabı kilitlendi", + "NotificationOptionVideoPlayback": "Video oynatma başladı", + "NotificationOptionVideoPlaybackStopped": "Video oynatma durduruldu", + "Photos": "Fotoğraflar", + "Playlists": "Çalma listeleri", + "Plugin": "Eklenti", + "PluginInstalledWithName": "{0} yüklendi", + "PluginUninstalledWithName": "{0} kaldırıldı", + "PluginUpdatedWithName": "{0} güncellendi", + "ProviderValue": "Sağlayıcı: {0}", + "ScheduledTaskFailedWithName": "{0} başarısız oldu", + "ScheduledTaskStartedWithName": "{0} başladı", + "ServerNameNeedsToBeRestarted": "{0} yeniden başlatılması gerekiyor", + "Shows": "Diziler", + "Songs": "Şarkılar", + "StartupEmbyServerIsLoading": "Jellyfin Sunucusu yükleniyor. Lütfen kısa süre sonra tekrar deneyin.", + "SubtitleDownloadFailureFromForItem": "{1} için altyazılar {0} sağlayıcısından indirilemedi", + "Sync": "Eşzamanlama", + "System": "Sistem", + "TvShows": "Diziler", + "User": "Kullanıcı", + "UserCreatedWithName": "{0} kullanıcısı oluşturuldu", + "UserDeletedWithName": "{0} kullanıcısı silindi", + "UserDownloadingItemWithValues": "{0} kullanıcısı {1} medyasını indiriyor", + "UserLockedOutWithName": "{0} adlı kullanıcı hesabı kilitlendi", + "UserOfflineFromDevice": "{0} kullanıcısının {1} ile bağlantısı kesildi", + "UserOnlineFromDevice": "{0} kullanıcısı {1} ile çevrimiçi", + "UserPasswordChangedWithName": "{0} kullanıcısının parolası değiştirildi", + "UserPolicyUpdatedWithName": "{0} için kullanıcı politikası güncellendi", + "UserStartedPlayingItemWithValues": "{0}, {2} cihazında {1} izliyor", + "UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi", + "ValueHasBeenAddedToLibrary": "{0} medya kütüphanenize eklendi", + "ValueSpecialEpisodeName": "Özel - {0}", + "VersionNumber": "Sürüm {0}", + "TaskCleanCache": "Önbellek Dizinini Temizle", + "TasksChannelsCategory": "İnternet Kanalları", + "TasksApplicationCategory": "Uygulama", + "TasksLibraryCategory": "Kütüphane", + "TasksMaintenanceCategory": "Bakım", + "TaskRefreshPeopleDescription": "Medya kütüphanenizdeki videoların oyuncu ve yönetmen bilgilerini günceller.", + "TaskDownloadMissingSubtitlesDescription": "Meta veri yapılandırmasına dayalı olarak eksik altyazılar için internette arama yapar.", + "TaskDownloadMissingSubtitles": "Eksik altyazıları indir", + "TaskRefreshChannelsDescription": "Internet kanal bilgilerini yenile.", + "TaskRefreshChannels": "Kanalları Yenile", + "TaskCleanTranscodeDescription": "Bir günden daha eski kod dönüştürme dosyalarını siler.", + "TaskCleanTranscode": "Kod Dönüştürme Dizinini Temizle", + "TaskUpdatePluginsDescription": "Otomatik güncellenmeye ayarlanmış eklentilerin güncellemelerini indirir ve kurar.", + "TaskUpdatePlugins": "Eklentileri Güncelle", + "TaskRefreshPeople": "Kullanıcıları Yenile", + "TaskCleanLogsDescription": "{0} günden eski günlük dosyalarını siler.", + "TaskCleanLogs": "Günlük Dizinini Temizle", + "TaskRefreshLibraryDescription": "Medya kütüphanenize eklenen yeni dosyaları arar ve ortam bilgilerini yeniler.", + "TaskRefreshLibrary": "Medya Kütüphanesini Tara", + "TaskRefreshChapterImagesDescription": "Bölümlere ayrılmış videolar için küçük resimler oluştur.", + "TaskRefreshChapterImages": "Bölüm Resimlerini Çıkar", + "TaskCleanCacheDescription": "Sistem tarafından artık ihtiyaç duyulmayan önbellek dosyalarını siler.", + "TaskCleanActivityLog": "Etkinlik Günlüğünü Temizle", + "TaskCleanActivityLogDescription": "Yapılandırılan tarihten daha eski olan etkinlik günlüğü girişlerini siler.", + "Undefined": "Tanımlanmadı", + "Default": "Varsayılan", + "Forced": "Zorla", + "TaskOptimizeDatabaseDescription": "Veritabanını sıkıştırır ve boş alanı keser. Kütüphaneyi taradıktan sonra veya veritabanında değişiklik anlamına gelen diğer işlemleri yaptıktan sonra bu görevi çalıştırmak performansı artırabilir.", + "TaskOptimizeDatabase": "Veritabanını optimize et", + "TaskKeyframeExtractorDescription": "Daha hassas HLS çalma listeleri oluşturmak için video dosyalarından ana kareleri çıkarır. Bu görev uzun bir süre çalışabilir.", + "TaskKeyframeExtractor": "Ana Kare Çıkarıcı", + "External": "Harici", + "HearingImpaired": "Duyma Engelli", + "TaskRefreshTrickplayImages": "Hızlı Önizleme Görsellerini Oluştur", + "TaskRefreshTrickplayImagesDescription": "Etkin kütüphanelerdeki videolar için hızlı önizleme görselleri oluşturur.", + "TaskCleanCollectionsAndPlaylistsDescription": "Artık var olmayan koleksiyon ve çalma listelerindeki ögeleri kaldırır.", + "TaskCleanCollectionsAndPlaylists": "Koleksiyonları ve çalma listelerini temizleyin", + "TaskAudioNormalizationDescription": "Ses normalleştirme verileri için dosyaları tarar.", + "TaskAudioNormalization": "Ses Normalleştirme", + "TaskExtractMediaSegments": "Medya Segmenti Tarama", + "TaskMoveTrickplayImages": "Hızlı Önizleme Görsel Konumunu Taşıma", + "TaskMoveTrickplayImagesDescription": "Mevcut hızlı önizleme dosyalarını kütüphane ayarlarına göre taşır.", + "TaskDownloadMissingLyrics": "Eksik şarkı sözlerini indir", + "TaskDownloadMissingLyricsDescription": "Şarkı sözlerini indirir", + "TaskExtractMediaSegmentsDescription": "MediaSegment özelliği etkin olan eklentilerden medya segmentlerini çıkarır veya alır.", + "CleanupUserDataTask": "Kullanıcı verisi temizleme görevi", + "CleanupUserDataTaskDescription": "En az 90 gün boyunca artık mevcut olmayan medyadaki tüm kullanıcı verilerini (İzleme durumu, favori durumu vb.) temizler." +} diff --git a/Emby.Server.Implementations/Localization/Core/ug.json b/Emby.Server.Implementations/Localization/Core/ug.json new file mode 100644 index 00000000..0bcbffb4 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ug.json @@ -0,0 +1,124 @@ +{ + "ChapterNameValue": "باب {0}", + "Channels": "قانال", + "CameraImageUploadedFrom": "{0} ئورۇندىن يېڭى سۈرەت چىقىرىلدى", + "Books": "كىتاب", + "AuthenticationSucceededWithUserName": "{0} تەستىقلاش مۇۋاپىقىيەتلىك بولدى", + "Artists": "سەنئەتكار", + "Albums": "پىلاستىنكا", + "DeviceOnlineWithName": "{0} ئۇلاندى", + "DeviceOfflineWithName": "{0} ئۈزۈلدى", + "Collections": "توپلام", + "Application": "ئەپ", + "AppDeviceValues": "ئەپ: {0}، ئۈسكۈنە: {1}", + "HeaderLiveTV": "تور تېلېۋىزىيەسى", + "Default": "سۈكۈتتىكى", + "Folders": "ھۆججەت خالتىسى", + "Favorites": "ساقلىغۇچ", + "LabelRunningTimeValue": "ئىجرا بولغان ۋاقتى:{0}", + "HeaderRecordingGroups": "خاتىرلەش گۇرۇپىسى", + "Forced": "ئەڭ", + "TaskKeyframeExtractor": "ھالقىلىق رامكا ئاجراتقۇچ", + "TaskKeyframeExtractorDescription": "سىن ھۆججەتلىرىدىن رامكا ئاجرىتىپ، تېخىمۇ ئېنىق بولغان HLS قويۇلۇش تىزىملىكىنى قۇرۇلىدۇ. بۇ ۋەزىپە ئۇزۇن داۋام قىلىشى مۇمكىن.", + "TaskOptimizeDatabase": "سانداننى ئەلالاشتۇرۇش", + "TaskDownloadMissingSubtitlesDescription": "ئامىللار تەڭشىكىگە ئاساسەن توردىن كەم بولغان فىلىم خېتىنى ئىزدەش.", + "TaskDownloadMissingSubtitles": "كەم بولغان فىلىم خەتلىرىنى چۈشۈرۈش", + "TasksChannelsCategory": "ئىنتېرنېت قاناللىرى", + "TaskRefreshChannelsDescription": "ئىنتېرنېت قانىلى ئۇچۇرىنى يېڭىلاش.", + "TaskRefreshChannels": "قانالنى يېڭىلاش", + "TaskCleanTranscodeDescription": "بىر كۈندىن ئاشقان ئالماشتۇرۇش ھۆججەتلىرىنى ئۆچۈرۈش.", + "TaskCleanTranscode": "ئايلاندۇرۇش ھۆججەت قىسقۇچىنى تازىلاش", + "TaskUpdatePluginsDescription": "ئاپتوماتىك يېڭىلاشقا بېكىتىلگەن قىستۇرمىلارنىڭ يېڭىلانمىسىنى چۈشۈرۈش ۋە قاچىلاش.", + "TaskUpdatePlugins": "قىستۇرمىلارنى يېڭىلاش", + "TaskRefreshPeopleDescription": "مېدىيا ئامبىرىدىكى ئارتىس ۋە رېژىسسورلارنىڭ ئۇچۇرىنى يېڭىلاش.", + "TaskRefreshPeople": "ئابونتلارنى يېڭىلاش", + "TaskCleanLogsDescription": "{0} كۈندىن ئاشقان Log ھۆججىتىنى ئۆچۈرۈش.", + "TaskCleanLogs": "Log ھۆججەت قسقۇچىنى تازىلاش", + "TaskRefreshLibraryDescription": "مېدىيا ئامبىرىغا قوشۇلغان يېڭى ھۆججەتلەرنى ئىزدەش ۋە مېدىيا ئۇچۇرلىرىنى يېڭىلاش.", + "TaskRefreshLibrary": "مېدىيا ئامبىرىنى سىكاننېرلاش", + "TaskRefreshChapterImagesDescription": "ۋېدىئو بۆلەكلىرى ئۈچۈن كىچىك سۈرەت ياساش.", + "TaskRefreshChapterImages": "بۆلەكلەر رەسىملىرىنى چىقىرىۋېلىش", + "TaskCleanCacheDescription": "سىستېما ئىھتىياجى يوق بولغان ۋاقىتلىق ھۆججەتلەرنى ئۆچۈرۈش.", + "TaskCleanCache": "ۋاقىتلىق ھۆججەت قىسقۇچنى تازىلاش", + "TaskCleanActivityLogDescription": "ۋاقىت تەڭشىكىدىن بۇرۇنقى پائالىيەت خاتىرىسى خاتىرىسىنى ئۆچۈرۈش.", + "TaskCleanActivityLog": "پروگرامما خاتىرىسىنى تازىلاش", + "TasksApplicationCategory": "پروگرامما", + "TasksLibraryCategory": "مېدىيا ئامبىرى", + "TasksMaintenanceCategory": "ئاسراش", + "VersionNumber": "نەشرى {0}", + "ValueSpecialEpisodeName": "خاسلىق - {0}", + "ValueHasBeenAddedToLibrary": "{0} مېدىيا ئامبىرىڭىزغا قوشۇلدى", + "UserStoppedPlayingItemWithValues": "{0}،{1} نى {2} دە قويۇنشتىن توختىدى", + "UserStartedPlayingItemWithValues": "{0}،{1} نى {2} دە قويۇۋاتىدۇ", + "UserPolicyUpdatedWithName": "ئابونتلار سىياسىتى {0} غا يېڭىلاندى", + "UserPasswordChangedWithName": "ئابونت{0} ئۈچۈن پارول ئۆزگەرتىلدى", + "UserOfflineFromDevice": "{0} بىلەن {1} نىڭ ئالاقىسى ئۈزۈلدى", + "UserLockedOutWithName": "ئابونت {0} قۇلۇپلاندى", + "UserDownloadingItemWithValues": "{0} چۈشۈرۈۋاتىدۇ {1}", + "UserDeletedWithName": "{0} ئابونت ئۆچۈرۈلدى", + "UserCreatedWithName": "{0} ئابونت يېڭىدىن قوشۇلدى", + "User": "ئابونت", + "Undefined": "بېكىتىلمىگەن", + "TvShows": "تىياتىرلار", + "System": "سىستېما", + "Sync": "ماس قەدەمدەش", + "SubtitleDownloadFailureFromForItem": "{0} دىن {0} نىڭ فىلىم خېتىنى چۈشۈرگىلى بولمىدى", + "StartupEmbyServerIsLoading": "Jellyfin مۇلازىمىتېرى يۈكلىنىۋاتىدۇ. سەل تۇرۇپ قايتا سىناڭ.", + "Songs": "ناخشىلار", + "Shows": "پروگراممىلار", + "ServerNameNeedsToBeRestarted": "{0} قايتا قوزغىتىلىشى كېرەك", + "ScheduledTaskStartedWithName": "{0} باشلاندى", + "ScheduledTaskFailedWithName": "{0} مەغلۇپ بولدى", + "ProviderValue": "تەمىنلىگۈچى: {0}", + "PluginUpdatedWithName": "{0} يېڭىلاندى", + "PluginUninstalledWithName": "{0} ئۆچۈرۈلدى", + "PluginInstalledWithName": "{0} قاچىلاندى", + "Plugin": "قىستۇرما", + "Playlists": "قويۇش تىزىملىكى", + "Photos": "رەسىملەر", + "NotificationOptionVideoPlaybackStopped": "سىن قويۇلۇش توختىدى", + "NotificationOptionVideoPlayback": "سىن قويۇلدى", + "NotificationOptionUserLockedOut": "ئابونت قۇلۇپلاندى", + "NotificationOptionTaskFailed": "بەلگىلەنگەن ۋەزىپە مەغلۇپ بولدى", + "NotificationOptionServerRestartRequired": "مۇلازىمىتېر قايتا قوزغىتىلىشى كېرەك", + "NotificationOptionPluginUpdateInstalled": "قىستۇرما يېڭىلانمىسى قاچىلاندى", + "NotificationOptionPluginInstalled": "قىستۇرما قاچىلاندى", + "NotificationOptionPluginUninstalled": "قىستۇرما ئۆچۈرۈلدى", + "NotificationOptionPluginError": "قىستۇرما خاتالىقى", + "NotificationOptionNewLibraryContent": "يېڭى مەزمۇن قوشۇلدى", + "NotificationOptionInstallationFailed": "قاچىلاش مەغلۇب بولدى", + "NotificationOptionCameraImageUploaded": "كامىكامېرا سۈرىتى يوللاندى", + "NotificationOptionAudioPlayback": "ئاۋاز قويۇش باشلاندى", + "NotificationOptionAudioPlaybackStopped": "ئاۋاز قويۇش توختىدى", + "NotificationOptionApplicationUpdateInstalled": "ئەپ يېڭىلانمىسى ئورنىتىلدى", + "NotificationOptionApplicationUpdateAvailable": "ئەپنىڭ نەشرىنى يېڭىلىغىلى بولۇدۇ", + "NewVersionIsAvailable": "Jellyfin Server نىڭ يېڭى نۇسخىسىنى چۈشۈرگىلى بولىدۇ.", + "NameSeasonUnknown": "نامەمۇم بۆلۈم", + "NameSeasonNumber": "{0}-بۆلۈم", + "NameInstallFailed": "{0} قاچىلاش مەغلۇپ بولدى", + "MusicVideos": "سىنلىق مۇزىكا", + "Music": "مۇزىكا", + "Movies": "فىلىملەر", + "MixedContent": "ئارىلاشما مەزمۇن", + "MessageNamedServerConfigurationUpdatedWithValue": "مۇلازىمىتېر تەڭشىكىنىڭ {0} قىسمى يېڭىلىنىپ بولدى", + "MessageServerConfigurationUpdated": "مۇلازىمىتېر يېڭىلىنىپ بولدى", + "MessageApplicationUpdated": "Jellyfin مۇلازىمىتېرى يېڭىلاندى", + "MessageApplicationUpdatedTo": "Jellyfin مۇلازىمىتېر نەشرى {0} گە يېڭىلاندى", + "Latest": "ئەڭ يېڭى", + "LabelIpAddressValue": "{0}: IP ئادرىسى", + "ItemRemovedWithName": "{0} ئامباردىن چىقىرىلدى", + "ItemAddedWithName": "{0} ئامبارغا قوشۇلدى", + "Inherit": "داۋاملاشتۇرۇش", + "HomeVideos": "ئائىلە سىنلىرى", + "HeaderNextUp": "كېيىنكىسى", + "HeaderFavoriteSongs": "ئەڭ ياقتۇرىدىغان ناخشىلار", + "HeaderFavoriteShows": "ئەڭ ياقتۇرىدىغان پروگراممىلار", + "HeaderFavoriteEpisodes": "ئەڭ ياقتۇرىدىغان تىياتېرلار", + "HeaderFavoriteArtists": "ئەڭ ياقتۇرىدىغان سەنئەتكارلار", + "HeaderFavoriteAlbums": "ياقتۇرىدىغان پىلاستىنكىلار", + "HeaderContinueWatching": "داۋاملىق كۆرۈش", + "HeaderAlbumArtists": "پىلاستىنكا سەنئەتكارلىرى", + "Genres": "ئۇسلۇبلار", + "FailedLoginAttemptWithUserName": "{0} كىرىش ئوڭۇشلۇق بولمىدى", + "External": "سىرتقى" +} diff --git a/Emby.Server.Implementations/Localization/Core/uk.json b/Emby.Server.Implementations/Localization/Core/uk.json new file mode 100644 index 00000000..3ad772aa --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/uk.json @@ -0,0 +1,141 @@ +{ + "MusicVideos": "Відеокліпи", + "Music": "Музика", + "Movies": "Фільми", + "MessageApplicationUpdatedTo": "Jellyfin Server оновлено до версії {0}", + "MessageApplicationUpdated": "Jellyfin Server оновлено", + "Latest": "Останні", + "LabelIpAddressValue": "IP-адреса: {0}", + "ItemRemovedWithName": "{0} видалено з медіатеки", + "ItemAddedWithName": "{0} додано до медіатеки", + "HeaderNextUp": "Наступний", + "HeaderLiveTV": "Ефірне ТБ", + "HeaderFavoriteSongs": "Обрані пісні", + "HeaderFavoriteShows": "Обрані шоу", + "HeaderFavoriteEpisodes": "Обрані епізоди", + "HeaderFavoriteArtists": "Обрані виконавці", + "HeaderFavoriteAlbums": "Обрані альбоми", + "HeaderContinueWatching": "Продовжити перегляд", + "HeaderAlbumArtists": "Виконавці альбому", + "Genres": "Жанри", + "Folders": "Теки", + "Favorites": "Обрані", + "DeviceOnlineWithName": "Пристрій {0} підключився", + "DeviceOfflineWithName": "Пристрій {0} відключився", + "Collections": "Колекції", + "ChapterNameValue": "Сцена {0}", + "Channels": "Канали", + "CameraImageUploadedFrom": "Нову фотографію завантажено з {0}", + "Books": "Книги", + "AuthenticationSucceededWithUserName": "{0} успішно авторизовано", + "Artists": "Виконавці", + "Application": "Додаток", + "AppDeviceValues": "Додаток: {0}, Пристрій: {1}", + "Albums": "Альбоми", + "NotificationOptionServerRestartRequired": "Необхідно перезапустити сервер", + "NotificationOptionPluginUpdateInstalled": "Встановлено оновлення плагіна", + "NotificationOptionPluginUninstalled": "Плагін видалено", + "NotificationOptionPluginInstalled": "Плагін встановлено", + "NotificationOptionPluginError": "Помилка плагіна", + "NotificationOptionNewLibraryContent": "Додано новий контент", + "HomeVideos": "Мої відео", + "FailedLoginAttemptWithUserName": "Невдала спроба входу від {0}", + "LabelRunningTimeValue": "Тривалість: {0}", + "TaskDownloadMissingSubtitlesDescription": "Шукає в Інтернеті відсутні субтитри на основі конфігурації метаданих.", + "TaskDownloadMissingSubtitles": "Завантажити відсутні субтитри", + "TaskRefreshChannelsDescription": "Оновлення інформації про Інтернет-канали.", + "TaskRefreshChannels": "Оновити канали", + "TaskCleanTranscodeDescription": "Вилучає файли для перекодування старше одного дня.", + "TaskCleanTranscode": "Очистити каталог перекодування", + "TaskUpdatePluginsDescription": "Завантажує та встановлює оновлення для плагінів, налаштованих на автоматичне оновлення.", + "TaskUpdatePlugins": "Оновити плагіни", + "TaskRefreshPeopleDescription": "Оновлення метаданих для акторів та режисерів у вашій медіатеці.", + "TaskRefreshPeople": "Оновити людей", + "TaskCleanLogsDescription": "Видаляє файли журналу, яким більше {0} днів.", + "TaskCleanLogs": "Очистити журнали", + "TaskRefreshLibraryDescription": "Сканує медіатеку на нові файли та оновлює метадані.", + "TaskRefreshLibrary": "Сканувати медіатеку", + "TaskRefreshChapterImagesDescription": "Створює ескізи для відео, які мають розділи.", + "TaskRefreshChapterImages": "Створити ескізи розділів", + "TaskCleanCacheDescription": "Видаляє файли кешу, які більше не потрібні системі.", + "TaskCleanCache": "Очистити кеш", + "TasksChannelsCategory": "Інтернет-канали", + "TasksApplicationCategory": "Додаток", + "TasksLibraryCategory": "Медіатека", + "TasksMaintenanceCategory": "Обслуговування", + "VersionNumber": "Версія {0}", + "ValueSpecialEpisodeName": "Спецепізод - {0}", + "ValueHasBeenAddedToLibrary": "{0} додано до медіатеки", + "UserStoppedPlayingItemWithValues": "{0} закінчив відтворення {1} на {2}", + "UserStartedPlayingItemWithValues": "{0} відтворює {1} на {2}", + "UserPolicyUpdatedWithName": "Політика користувача оновлена для {0}", + "UserPasswordChangedWithName": "Пароль змінено для користувача {0}", + "UserOnlineFromDevice": "{0} підключився з {1}", + "UserOfflineFromDevice": "{0} відключився від {1}", + "UserLockedOutWithName": "Користувача {0} заблоковано", + "UserDownloadingItemWithValues": "{0} завантажує {1}", + "UserDeletedWithName": "Користувача {0} видалено", + "UserCreatedWithName": "Користувача {0} створено", + "User": "Користувач", + "TvShows": "ТВ-шоу", + "System": "Система", + "Sync": "Синхронізація", + "SubtitleDownloadFailureFromForItem": "Не вдалося завантажити субтитри з {0} для {1}", + "StartupEmbyServerIsLoading": "Jellyfin Server завантажується. Будь ласка, спробуйте трішки пізніше.", + "Songs": "Пісні", + "Shows": "Серіали", + "ServerNameNeedsToBeRestarted": "{0} потрібно перезапустити", + "ScheduledTaskStartedWithName": "{0} розпочато", + "ScheduledTaskFailedWithName": "{0} незавершено, збій", + "ProviderValue": "Постачальник: {0}", + "PluginUpdatedWithName": "{0} оновлено", + "PluginUninstalledWithName": "{0} видалено", + "PluginInstalledWithName": "{0} встановлено", + "Plugin": "Плагін", + "Playlists": "Плейлисти", + "Photos": "Фотографії", + "NotificationOptionVideoPlaybackStopped": "Відтворення відео зупинено", + "NotificationOptionVideoPlayback": "Розпочато відтворення відео", + "NotificationOptionUserLockedOut": "Користувача заблоковано", + "NotificationOptionTaskFailed": "Помилка запланованого завдання", + "NotificationOptionInstallationFailed": "Помилка встановлення", + "NotificationOptionCameraImageUploaded": "Фотографію завантажено", + "NotificationOptionAudioPlaybackStopped": "Відтворення аудіо зупинено", + "NotificationOptionAudioPlayback": "Розпочато відтворення аудіо", + "NotificationOptionApplicationUpdateInstalled": "Встановлено оновлення додатка", + "NotificationOptionApplicationUpdateAvailable": "Доступне оновлення додатка", + "NewVersionIsAvailable": "Для завантаження доступна нова версія Jellyfin Server.", + "NameSeasonUnknown": "Сезон Невідомий", + "NameSeasonNumber": "Сезон {0}", + "NameInstallFailed": "Не вдалося встановити {0}", + "MixedContent": "Змішаний контент", + "MessageServerConfigurationUpdated": "Конфігурація сервера оновлена", + "MessageNamedServerConfigurationUpdatedWithValue": "Розділ конфігурації сервера {0} оновлено", + "Inherit": "Успадкувати", + "HeaderRecordingGroups": "Групи запису", + "Forced": "Форсовані", + "TaskCleanActivityLogDescription": "Видаляє старші за встановлений термін записи з журналу активності.", + "TaskCleanActivityLog": "Очистити журнал активності", + "Undefined": "Не визначено", + "Default": "За замовчуванням", + "TaskOptimizeDatabase": "Оптимізувати базу даних", + "TaskOptimizeDatabaseDescription": "Стискає базу даних та збільшує вільний простір. Виконання цього завдання після сканування медіатеки або внесення інших змін, які передбачають модифікацію бази даних може покращити продуктивність.", + "TaskKeyframeExtractorDescription": "Витягує ключові кадри з відеофайлів для створення більш точних списків відтворення HLS. Це завдання може виконуватися протягом тривалого часу.", + "TaskKeyframeExtractor": "Екстрактор ключових кадрів", + "External": "Зовнішній", + "HearingImpaired": "З порушеннями слуху", + "TaskRefreshTrickplayImagesDescription": "Створює trickplay-зображення для відео у ввімкнених медіатеках.", + "TaskRefreshTrickplayImages": "Створити Trickplay-зображення", + "TaskCleanCollectionsAndPlaylists": "Очистити колекції і списки відтворення", + "TaskCleanCollectionsAndPlaylistsDescription": "Видаляє елементи з колекцій і списків відтворення, які більше не існують.", + "TaskAudioNormalizationDescription": "Сканує файли на наявність даних для нормалізації звуку.", + "TaskAudioNormalization": "Нормалізація аудіо", + "TaskDownloadMissingLyrics": "Завантажити відсутні тексти пісень", + "TaskDownloadMissingLyricsDescription": "Завантаження текстів пісень", + "TaskMoveTrickplayImagesDescription": "Переміщує наявні Trickplay-зображення відповідно до налаштувань медіатеки.", + "TaskExtractMediaSegments": "Сканування медіа-сегментів", + "TaskMoveTrickplayImages": "Змінити місце розташування Trickplay-зображень", + "TaskExtractMediaSegmentsDescription": "Витягує або отримує медіа-сегменти з плагінів з підтримкою MediaSegment.", + "CleanupUserDataTask": "Завдання очищення даних користувача", + "CleanupUserDataTaskDescription": "Очищає всі дані користувача (стан перегляду, статус обраного тощо) з медіа, які перестали бути доступними щонайменше 90 днів тому." +} diff --git a/Emby.Server.Implementations/Localization/Core/ur.json b/Emby.Server.Implementations/Localization/Core/ur.json new file mode 100644 index 00000000..94d9c854 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ur.json @@ -0,0 +1,16 @@ +{ + "Books": "کتابیں", + "AppDeviceValues": "ایپ: {0}، ڈیوائس: {1}", + "Albums": "البمز", + "Application": "ایپلی کیشن", + "Artists": "فنکار", + "AuthenticationSucceededWithUserName": "{0} کی کامیابی سے تصدیق ہو چکی ہے", + "CameraImageUploadedFrom": "ایک نئی کیمرے کی تصویر {0} سے اپ لوڈ کی گئی ہے", + "Channels": "چینلز", + "ChapterNameValue": "باب {0}", + "Collections": "مجموعے", + "Default": "ڈیفالٹ", + "DeviceOfflineWithName": "{0} نے رابطہ منقطع کر دیا ہے", + "DeviceOnlineWithName": "{0} منسلک ہے", + "External": "بیرونی" +} diff --git a/Emby.Server.Implementations/Localization/Core/ur_PK.json b/Emby.Server.Implementations/Localization/Core/ur_PK.json new file mode 100644 index 00000000..f6539adf --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ur_PK.json @@ -0,0 +1,131 @@ +{ + "HeaderFavoriteAlbums": "پسندیدہ البمز", + "HeaderNextUp": "اگلا", + "HeaderFavoriteArtists": "پسندیدہ فنکار", + "HeaderAlbumArtists": "البم کے فنکار", + "Movies": "فلمیں", + "HeaderFavoriteEpisodes": "پسندیدہ اقساط", + "Collections": "مجموعے", + "Folders": "فولڈرز", + "HeaderLiveTV": "براہ راست ٹی وی", + "Channels": "چینلز", + "HeaderContinueWatching": "دیکھنا جاری رکھیں", + "Playlists": "پلے لسٹس", + "ValueSpecialEpisodeName": "خصوصی - {0}", + "Shows": "دکھاتا ہے", + "Genres": "انواع", + "Artists": "فنکار", + "Sync": "مطابقت پذیری", + "Photos": "تصاویر", + "Albums": "البمز", + "Favorites": "پسندیدہ", + "Songs": "گانے", + "Books": "کتابیں", + "HeaderFavoriteSongs": "پسندیدہ گانے", + "HeaderFavoriteShows": "پسندیدہ شوز", + "TaskDownloadMissingSubtitlesDescription": "میٹا ڈیٹا کی تشکیل پر مبنی ذیلی عنوانات کے غائب عنوانات انٹرنیٹ پے تلاش کرتا ہے۔", + "TaskDownloadMissingSubtitles": "غائب سب ٹائٹلز ڈاؤن لوڈ کریں", + "TaskRefreshChannelsDescription": "انٹرنیٹ چینل کی معلومات کو تازہ دم کرتا ہے۔", + "TaskRefreshChannels": "چینلز ریفریش کریں", + "TaskCleanTranscodeDescription": "ایک دن سے زیادہ پرانی ٹرانسکوڈ فائلوں کو حذف کرتا ہے۔", + "TaskCleanTranscode": "ٹرانس کوڈ ڈائرکٹری صاف کریں", + "TaskUpdatePluginsDescription": "پلگ انز کے لئے اپ ڈیٹس ڈاؤن لوڈ اور انسٹال کرتے ہیں جو خود بخود اپ ڈیٹ کرنے کیلئے تشکیل شدہ ہیں۔", + "TaskUpdatePlugins": "پلگ انز کو اپ ڈیٹ کریں", + "TaskRefreshPeopleDescription": "آپ کی میڈیا لائبریری میں اداکاروں اور ہدایت کاروں کے لئے میٹا ڈیٹا کی تازہ کاری۔", + "TaskRefreshPeople": "لوگوں کو تروتازہ کریں", + "TaskCleanLogsDescription": "لاگ فائلوں کو حذف کریں جو {0} دن سے زیادہ پرانی ہیں۔", + "TaskCleanLogs": "لاگ ڈائرکٹری کو صاف کریں", + "TaskRefreshLibraryDescription": "میڈیا لائبریری کو اسکین کرتا ھے ہر میٹا دیٹا کہ تازہ دم کرتا ھے.", + "TaskRefreshLibrary": "اسکین میڈیا لائبریری", + "TaskRefreshChapterImagesDescription": "بابوں والی ویڈیوز کے لئے تمبنیل بنایں۔", + "TaskRefreshChapterImages": "باب کی تصاویر نکالیں", + "TaskCleanCacheDescription": "فائلوں کو حذف کریں جنکی ضرورت نھیں ھے۔", + "TaskCleanCache": "کیش ڈائرکٹری کلیر کریں", + "TasksChannelsCategory": "انٹرنیٹ چینلز", + "TasksApplicationCategory": "پروگرام", + "TasksLibraryCategory": "لآیبریری", + "TasksMaintenanceCategory": "مرمت", + "VersionNumber": "ورژن {0}", + "ValueHasBeenAddedToLibrary": "{0} آپ کی میڈیا لائبریری میں شامل کر دیا گیا ہے", + "UserStoppedPlayingItemWithValues": "{0} نے {1} چلانا ختم کر دیا ھے {2} پے", + "UserStartedPlayingItemWithValues": "{0} چلا رہا ہے {1} {2} پے", + "UserPolicyUpdatedWithName": "صارف {0} کی پالیسی کیلئے تازہ کاری کی گئی ہے", + "UserPasswordChangedWithName": "صارف {0} کے لئے پاس ورڈ تبدیل کر دیا گیا ہے", + "UserOnlineFromDevice": "{0} آن لائن ہے {1} سے", + "UserOfflineFromDevice": "{0} سے منقطع ہوگیا ہے {1}", + "UserLockedOutWithName": "صارف {0} کو لاک آؤٹ کردیا گیا ہے", + "UserDownloadingItemWithValues": "{0} ڈاؤن لوڈ کر رھا ھے {1}", + "UserDeletedWithName": "صارف {0} کو ہٹا دیا گیا ہے", + "UserCreatedWithName": "صارف {0} تشکیل دیا گیا ہے", + "User": "صارف", + "TvShows": "ٹی وی کے پروگرام", + "System": "نظام", + "SubtitleDownloadFailureFromForItem": "ذیلی عنوانات {0} سے ڈاؤن لوڈ کرنے میں ناکام {1} کے لیے", + "StartupEmbyServerIsLoading": "جیلیفن سرور لوڈ ہورہا ہے۔ براہ کرم جلد ہی دوبارہ کوشش کریں۔", + "ServerNameNeedsToBeRestarted": "{0} دوبارہ چلانے کرنے کی ضرورت ہے", + "ScheduledTaskStartedWithName": "{0} شروع", + "ScheduledTaskFailedWithName": "{0} ناکام", + "ProviderValue": "فراہم کرنے والا: {0}", + "PluginUpdatedWithName": "{0} تازہ کاری کی گئی تھی", + "PluginUninstalledWithName": "[0} ہٹا دیا گیا تھا", + "PluginInstalledWithName": "{0} انسٹال کیا گیا تھا", + "Plugin": "پلگن", + "NotificationOptionVideoPlaybackStopped": "ویڈیو پلے بیک رک گیا", + "NotificationOptionVideoPlayback": "ویڈیو پلے بیک شروع ہوا", + "NotificationOptionUserLockedOut": "صارف کو لاک آؤٹ کیا گیا", + "NotificationOptionTaskFailed": "طے شدہ کام کی ناکامی", + "NotificationOptionServerRestartRequired": "سرور دوبارہ چلانے کرنے کی ضرورت ہے", + "NotificationOptionPluginUpdateInstalled": "پلگ ان اپ ڈیٹ انسٹال", + "NotificationOptionPluginUninstalled": "پلگ ان ہٹا دیا گیا", + "NotificationOptionPluginInstalled": "پلگ ان انسٹال ہوا", + "NotificationOptionPluginError": "پلگ ان کی ناکامی", + "NotificationOptionNewLibraryContent": "نیا مواد شامل کیا گیا", + "NotificationOptionInstallationFailed": "تنصیب کی ناکامی", + "NotificationOptionCameraImageUploaded": "کیمرے کی تصویر اپ لوڈ ہوگئی", + "NotificationOptionAudioPlaybackStopped": "آڈیو پلے بیک رک گیا", + "NotificationOptionAudioPlayback": "آڈیو پلے بیک شروع ہوا", + "NotificationOptionApplicationUpdateInstalled": "پروگرام اپ ڈیٹ انسٹال ہوچکا ھے", + "NotificationOptionApplicationUpdateAvailable": "پروگرام کی تازہ کاری دستیاب ہے", + "NewVersionIsAvailable": "جیلیفن سرور کا ایک نیا ورژن ڈاؤن لوڈ کے لئے دستیاب ہے۔", + "NameSeasonUnknown": "نامعلوم باب", + "NameSeasonNumber": "باب {0}", + "NameInstallFailed": "{0} تنصیب ناکام ہوگئی", + "MusicVideos": "میوزک ویڈیوز", + "Music": "موسیقی", + "MixedContent": "مخلوط مواد", + "MessageServerConfigurationUpdated": "سرور کو اپ ڈیٹ کر دیا گیا ہے", + "MessageNamedServerConfigurationUpdatedWithValue": "سرور ضمن {0} کو ترتیب دے دیا گیا ھے", + "MessageApplicationUpdatedTo": "جیلیفن سرور کو اپ ڈیٹ کیا ہے {0}", + "MessageApplicationUpdated": "جیلیفن سرور کو اپ ڈیٹ کر دیا گیا ہے", + "Latest": "تازہ ترین", + "LabelRunningTimeValue": "چلانے کی مدت", + "LabelIpAddressValue": "آئ پی ایڈریس {0}", + "ItemRemovedWithName": "لائبریری سے ہٹا دیا گیا ھے", + "ItemAddedWithName": "[0} لائبریری میں شامل کیا گیا ھے", + "Inherit": "وراثت", + "HomeVideos": "ہوم ویڈیوز", + "HeaderRecordingGroups": "ریکارڈنگ گروپس", + "FailedLoginAttemptWithUserName": "{0} سے لاگ ان کی ناکام کوشش", + "DeviceOnlineWithName": "{0} متصل ھو چکا ھے", + "DeviceOfflineWithName": "{0} منقطع ھو چکا ھے", + "ChapterNameValue": "باب", + "AuthenticationSucceededWithUserName": "{0} کامیابی کے ساتھ تصدیق ھوچکی ھے", + "CameraImageUploadedFrom": "ایک نئی کیمرہ تصویر اپ لوڈ کی گئی ہے {0}", + "Application": "پروگرام", + "AppDeviceValues": "پروگرام:{0}, ڈیوائس:{1}", + "Forced": "جَبری", + "Undefined": "غير وضاحتى", + "Default": "طے شدہ", + "TaskKeyframeExtractorDescription": "زیادہ درست HLS پلے لسٹس بنانے کے لیے ویڈیو فائلوں سے کلیدی فریم نکالتا ہے۔ یہ کام طویل عرصے تک چل سکتا ہے۔", + "TaskOptimizeDatabase": "ڈیٹا بیس کو بہتر بنائیں", + "TaskOptimizeDatabaseDescription": "ڈیٹا بیس کو کمپیکٹ کرتا ہے اور خالی جگہ کو چھوٹا کرتا ہے۔ لائبریری کو اسکین کرنے یا دیگر تبدیلیاں کرنے کے بعد اس کام کو چلانے سے کارکردگی بہتر ہو سکتی ہے۔", + "TaskKeyframeExtractor": "کی فریم ایکسٹریکٹر", + "TaskCleanActivityLogDescription": "تشکیل شدہ عمر سے زیادہ پرانی سرگرمی لاگ اندراجات کو حذف کرتا ہے۔", + "External": "بیرونی", + "HearingImpaired": "قوت سماعت سے محروم", + "TaskCleanActivityLog": "سرگرمی لاگ کو صاف کریں", + "TaskDownloadMissingLyrics": "غائب بول ڈاؤن لوڈ کریں", + "TaskDownloadMissingLyricsDescription": "گانے کے غائب بول ڈاؤن لوڈ کریں", + "TaskAudioNormalization": "آڈیو نارملائزیشن", + "TaskAudioNormalizationDescription": "آڈیو نارملائزیشن ڈیٹا کے لیے فائلوں کو سکین کرتا ہے۔" +} diff --git a/Emby.Server.Implementations/Localization/Core/uz.json b/Emby.Server.Implementations/Localization/Core/uz.json new file mode 100644 index 00000000..e44b3f51 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/uz.json @@ -0,0 +1,115 @@ +{ + "HeaderContinueWatching": "Ko‘rishda davom etish", + "HeaderAlbumArtists": "Albom ijrochilari", + "Genres": "Janrlar", + "Folders": "Jildlar", + "Favorites": "Sevimlilar", + "Collections": "To'plamlar", + "Channels": "Kanallar", + "Books": "Kitoblar", + "Artists": "Ijrochilar", + "Albums": "Albomlar", + "AuthenticationSucceededWithUserName": "{0} muvaffaqiyatli tasdiqlandi", + "AppDeviceValues": "Ilova: {0}, Qurilma: {1}", + "Application": "Ilova", + "CameraImageUploadedFrom": "{0}dan yangi kamera rasmi yuklandi", + "DeviceOnlineWithName": "{0} ulangan", + "ItemRemovedWithName": "{0} kutbxonadan o'chirildi", + "External": "Tashqi", + "FailedLoginAttemptWithUserName": "Muvafaqiyatsiz kirishlar soni {0}", + "Forced": "Majburiy", + "ChapterNameValue": "{0}chi bo'lim", + "DeviceOfflineWithName": "{0} aloqa uzildi", + "HeaderLiveTV": "Jonli TV", + "HeaderNextUp": "Keyingisi", + "ItemAddedWithName": "{0} kutbxonaga qo'shildi", + "LabelIpAddressValue": "IP manzil: {0}", + "SubtitleDownloadFailureFromForItem": "{0} dan {1} uchun taglavhalarni yuklab boʻlmadi", + "UserPasswordChangedWithName": "Foydalanuvchi {0} paroli oʻzgartirildi", + "ValueHasBeenAddedToLibrary": "{0} kutubxonaga qoʻshildi", + "TaskCleanActivityLogDescription": "Belgilangan yoshdan kattaroq faoliyat jurnali yozuvlarini oʻchiradi.", + "TaskAudioNormalization": "Ovozni normallashtirish", + "TaskRefreshLibraryDescription": "Media kutubxonasi yangi fayllar uchun skanerlanmoqda va metama'lumotlar yangilanmoqda.", + "Default": "Joriy", + "HeaderFavoriteAlbums": "Tanlangan albomlar", + "HeaderFavoriteArtists": "Tanlangan artistlar", + "HeaderFavoriteEpisodes": "Tanlangan epizodlar", + "HeaderFavoriteShows": "Tanlangan shoular", + "HeaderFavoriteSongs": "Tanlangan qo'shiqlar", + "HeaderRecordingGroups": "Yozuvlar guruhi", + "HomeVideos": "Uy videolari", + "NotificationOptionVideoPlaybackStopped": "Video ijrosi toʻxtatildi", + "TvShows": "TV seriallar", + "Undefined": "Belgilanmagan", + "User": "Foydalanuvchi", + "UserCreatedWithName": "{0} foydalanuvchi yaratildi", + "TaskCleanCacheDescription": "Tizimga kerak bo'lmagan kesh fayllari o'chiriladi.", + "TaskAudioNormalizationDescription": "Ovozni normallashtirish ma'lumotlari uchun fayllarni skanerlaydi.", + "PluginInstalledWithName": "{0} - o'rnatildi", + "PluginUninstalledWithName": "{0} - o'chirildi", + "HearingImpaired": "Yaxshi eshitmaydiganlar uchun", + "Inherit": "Meroslangan", + "NotificationOptionApplicationUpdateAvailable": "Ilova yangilanishi mavjud", + "NotificationOptionApplicationUpdateInstalled": "Ilova yangilanishi oʻrnatildi", + "LabelRunningTimeValue": "Davomiyligi", + "NotificationOptionAudioPlayback": "Audio tinglash boshlandi", + "NotificationOptionAudioPlaybackStopped": "Audio tinglash to'xtatildi", + "NotificationOptionCameraImageUploaded": "Kamera tasvirlari yuklandi", + "NotificationOptionInstallationFailed": "O'rnatishda hatolik", + "NotificationOptionNewLibraryContent": "Yangi tarkib qo'shildi", + "NotificationOptionPluginError": "Plagin ishdan chiqdi", + "NotificationOptionPluginInstalled": "Plagin o'rnatildi", + "NotificationOptionPluginUninstalled": "Plagin o'chirildi", + "NotificationOptionPluginUpdateInstalled": "Plagin uchun yangilanish o'rnatildi", + "NotificationOptionServerRestartRequired": "Server-ni qayta yuklash lozim", + "NotificationOptionTaskFailed": "Rejalashtirilgan vazifa bajarilmadi", + "NotificationOptionUserLockedOut": "Foydalanuvchi bloklangan", + "NotificationOptionVideoPlayback": "Video ijrosi boshlandi", + "Photos": "Surat", + "Latest": "So'ngi", + "MessageApplicationUpdated": "Jellyfin Server yangilandi", + "MessageApplicationUpdatedTo": "Jellyfin Server {0} gacha yangilandi", + "MessageNamedServerConfigurationUpdatedWithValue": "Server konfiguratsiyasi ({0}-boʻlim) yangilandi", + "MessageServerConfigurationUpdated": "Server konfiguratsiyasi yangilandi", + "MixedContent": "Aralashgan tarkib", + "Movies": "Kinolar", + "Music": "Qo'shiqlar", + "MusicVideos": "Musiqali videolar", + "NameInstallFailed": "Omadsiz ornatish {0}", + "NameSeasonNumber": "{0} Fasl", + "NameSeasonUnknown": "Fasl aniqlanmagan", + "Playlists": "Pleylistlar", + "NewVersionIsAvailable": "Yuklab olish uchun Jellyfin Server ning yangi versiyasi mavjud", + "Plugin": "Plagin", + "TaskCleanLogs": "Jurnallar katalogini tozalash", + "PluginUpdatedWithName": "{0} - yangilandi", + "ProviderValue": "Yetkazib beruvchi: {0}", + "ScheduledTaskFailedWithName": "{0} - omadsiz", + "ScheduledTaskStartedWithName": "{0} - ishga tushirildi", + "ServerNameNeedsToBeRestarted": "Qayta yuklash kerak {0}", + "Shows": "Teleko'rsatuv", + "Songs": "Kompozitsiyalar", + "StartupEmbyServerIsLoading": "Jellyfin Server yuklanmoqda. Tez orada qayta urinib koʻring.", + "Sync": "Sinxronizatsiya", + "System": "Tizim", + "UserDeletedWithName": "{0} foydalanuvchisi oʻchirib tashlandi", + "UserDownloadingItemWithValues": "{0} yuklanmoqda {1}", + "UserLockedOutWithName": "{0} foydalanuvchisi bloklandi", + "UserOfflineFromDevice": "{0} {1}dan uzildi", + "UserOnlineFromDevice": "{0} {1} dan ulandi", + "UserPolicyUpdatedWithName": "{0} foydalanuvchisining siyosatlari yangilandi", + "UserStartedPlayingItemWithValues": "{0} - {2} da \"{1}\" ijrosi", + "UserStoppedPlayingItemWithValues": "{0} - ijro etish to‘xtatildi {1} {2}", + "ValueSpecialEpisodeName": "Maxsus qism – {0}", + "VersionNumber": "Versiya {0}", + "TasksMaintenanceCategory": "Xizmat ko'rsatish", + "TasksLibraryCategory": "Media kutubxona", + "TasksApplicationCategory": "Ilova", + "TasksChannelsCategory": "Internet kanallari", + "TaskCleanActivityLog": "Faoliyat jurnalini tozalash", + "TaskCleanCache": "Kesh katalogini tozalash", + "TaskRefreshChapterImages": "Sahnadan tasvirini chiqarish", + "TaskRefreshChapterImagesDescription": "Sahnalarni o'z ichiga olgan videolar uchun eskizlarni yaratadi.", + "TaskRefreshLibrary": "Media kutubxonangizni skanerlash", + "TaskCleanLogsDescription": "{0} kundan eski log fayllarni o'chiradi." +} diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json new file mode 100644 index 00000000..3f4bf1f7 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/vi.json @@ -0,0 +1,141 @@ +{ + "Collections": "Bộ Sưu Tập", + "Favorites": "Yêu Thích", + "Folders": "Thư Mục", + "Genres": "Thể Loại", + "HeaderAlbumArtists": "Album nghệ sĩ", + "HeaderContinueWatching": "Xem Tiếp", + "HeaderLiveTV": "TV Trực Tiếp", + "Movies": "Phim", + "Photos": "Ảnh", + "Playlists": "Danh sách phát", + "Shows": "Chương Trình TV", + "Songs": "Bài Hát", + "Sync": "Đồng Bộ", + "ValueSpecialEpisodeName": "Đặc Biệt - {0}", + "Albums": "Album", + "Artists": "Ca Sĩ", + "TaskDownloadMissingSubtitlesDescription": "Tìm kiếm phụ đề bị thiếu trên Internet dựa trên cấu hình dữ liệu mô tả.", + "TaskDownloadMissingSubtitles": "Tải Xuống Phụ Đề Bị Thiếu", + "TaskRefreshChannelsDescription": "Làm mới thông tin kênh internet.", + "TaskRefreshChannels": "Làm Mới Kênh", + "TaskCleanTranscodeDescription": "Xóa các tệp chuyển mã cũ hơn một ngày.", + "TaskCleanTranscode": "Làm Sạch Thư Mục Chuyển Mã", + "TaskUpdatePluginsDescription": "Tải xuống và cài đặt các bản cập nhật cho các plugin được định cấu hình để cập nhật tự động.", + "TaskUpdatePlugins": "Cập Nhật Plugins", + "TaskRefreshPeopleDescription": "Cập nhật thông tin chi tiết cho diễn viên và đạo diễn trong thư viện phương tiện của bạn.", + "TaskRefreshPeople": "Làm Mới Người Dùng", + "TaskCleanLogsDescription": "Xóa tập tin nhật ký cũ hơn {0} ngày.", + "TaskCleanLogs": "Làm Sạch Thư Mục Nhật Ký", + "TaskRefreshLibraryDescription": "Quét thư viện phương tiện của bạn để tìm tệp mới và làm mới dữ liệu mô tả.", + "TaskRefreshLibrary": "Quét Thư Viện Phương Tiện", + "TaskRefreshChapterImagesDescription": "Tạo hình thu nhỏ cho video có các phân cảnh.", + "TaskRefreshChapterImages": "Trích Xuất Ảnh Phân Cảnh", + "TaskCleanCacheDescription": "Xóa các tệp cache không còn cần thiết của hệ thống.", + "TaskCleanCache": "Làm Sạch Thư Mục Bộ Nhớ Đệm", + "TasksChannelsCategory": "Kênh Internet", + "TasksApplicationCategory": "Ứng Dụng", + "TasksLibraryCategory": "Thư Viện", + "TasksMaintenanceCategory": "Bảo Trì", + "VersionNumber": "Phiên Bản {0}", + "ValueHasBeenAddedToLibrary": "{0} đã được thêm vào thư viện của bạn", + "UserStoppedPlayingItemWithValues": "{0} đã kết thúc phát {1} trên {2}", + "UserStartedPlayingItemWithValues": "{0} đang phát {1} trên {2}", + "UserPolicyUpdatedWithName": "Chính sách người dùng đã được cập nhật cho {0}", + "UserPasswordChangedWithName": "Mật khẩu đã được thay đổi cho người dùng {0}", + "UserOnlineFromDevice": "{0} trực tuyến từ {1}", + "UserOfflineFromDevice": "{0} đã ngắt kết nối từ {1}", + "UserLockedOutWithName": "User {0} đã bị khóa", + "UserDownloadingItemWithValues": "{0} đang tải xuống {1}", + "UserDeletedWithName": "Người Dùng {0} đã được xóa", + "UserCreatedWithName": "Người Dùng {0} đã được tạo", + "User": "Người Dùng", + "TvShows": "Chương Trình TV", + "System": "Hệ Thống", + "SubtitleDownloadFailureFromForItem": "Không thể tải xuống phụ đề từ {0} cho {1}", + "StartupEmbyServerIsLoading": "Jellyfin Server đang tải. Vui lòng thử lại trong thời gian ngắn.", + "ServerNameNeedsToBeRestarted": "{0} cần được khởi động lại", + "ScheduledTaskStartedWithName": "{0} đã bắt đầu", + "ScheduledTaskFailedWithName": "{0} đã thất bại", + "ProviderValue": "Provider: {0}", + "PluginUpdatedWithName": "{0} đã cập nhật", + "PluginUninstalledWithName": "{0} đã được gỡ bỏ", + "PluginInstalledWithName": "{0} đã được cài đặt", + "Plugin": "Plugin", + "NotificationOptionVideoPlaybackStopped": "Đã dừng phát lại video", + "NotificationOptionVideoPlayback": "Đã bắt đầu phát lại video", + "NotificationOptionUserLockedOut": "Người dùng bị khóa", + "NotificationOptionTaskFailed": "Lỗi tác vụ đã lên lịch", + "NotificationOptionServerRestartRequired": "Yêu cầu khởi động lại máy chủ", + "NotificationOptionPluginUpdateInstalled": "Cập nhật Plugin đã được cài đặt", + "NotificationOptionPluginUninstalled": "Đã gỡ bỏ Plugin", + "NotificationOptionPluginInstalled": "Đã cài đặt Plugin", + "NotificationOptionPluginError": "Thất bại Plugin", + "NotificationOptionNewLibraryContent": "Nội dung mới được thêm vào", + "NotificationOptionInstallationFailed": "Cài đặt thất bại", + "NotificationOptionCameraImageUploaded": "Đã tải lên hình ảnh máy ảnh", + "NotificationOptionAudioPlaybackStopped": "Phát lại âm thanh đã dừng", + "NotificationOptionAudioPlayback": "Đã bắt đầu phát lại âm thanh", + "NotificationOptionApplicationUpdateInstalled": "Bản cập nhật ứng dụng đã được cài đặt", + "NotificationOptionApplicationUpdateAvailable": "Bản cập nhật ứng dụng hiện sẵn có", + "NewVersionIsAvailable": "Một phiên bản mới của Jellyfin Server sẵn có để tải.", + "NameSeasonUnknown": "Không Rõ Mùa", + "NameSeasonNumber": "Phần {0}", + "NameInstallFailed": "{0} cài đặt thất bại", + "MusicVideos": "Videos Nhạc", + "Music": "Nhạc", + "MixedContent": "Nội dung hỗn hợp", + "MessageServerConfigurationUpdated": "Cấu hình máy chủ đã được cập nhật", + "MessageNamedServerConfigurationUpdatedWithValue": "Phần cấu hình máy chủ {0} đã được cập nhật", + "MessageApplicationUpdatedTo": "Jellyfin Server đã được cập nhật lên {0}", + "MessageApplicationUpdated": "Jellyfin Server đã được cập nhật", + "Latest": "Gần Nhất", + "LabelRunningTimeValue": "Thời Gian Chạy: {0}", + "LabelIpAddressValue": "Địa chỉ IP: {0}", + "ItemRemovedWithName": "{0} đã xóa khỏi thư viện", + "ItemAddedWithName": "{0} được thêm vào thư viện", + "Inherit": "Thừa hưởng", + "HomeVideos": "Video Nhà", + "HeaderRecordingGroups": "Nhóm Ghi Video", + "HeaderNextUp": "Tiếp Theo", + "HeaderFavoriteSongs": "Bài Hát Yêu Thích", + "HeaderFavoriteShows": "Chương Trình Yêu Thích", + "HeaderFavoriteEpisodes": "Tập Phim Yêu Thích", + "HeaderFavoriteArtists": "Nghệ Sĩ Yêu Thích", + "HeaderFavoriteAlbums": "Album Ưa Thích", + "FailedLoginAttemptWithUserName": "Nỗ lực đăng nhập không thành công từ {0}", + "DeviceOnlineWithName": "{0} đã kết nối", + "DeviceOfflineWithName": "{0} đã ngắt kết nối", + "ChapterNameValue": "Phân Cảnh {0}", + "Channels": "Kênh", + "CameraImageUploadedFrom": "Một hình ảnh máy ảnh mới đã được tải lên từ {0}", + "Books": "Sách", + "AuthenticationSucceededWithUserName": "{0} xác thực thành công", + "Application": "Ứng Dụng", + "AppDeviceValues": "Ứng Dụng: {0}, Thiết Bị: {1}", + "TaskCleanActivityLogDescription": "Xóa các mục nhật ký hoạt động cũ hơn độ tuổi đã cài đặt.", + "TaskCleanActivityLog": "Xóa Nhật Ký Hoạt Động", + "Undefined": "Không Xác Định", + "Forced": "Bắt Buộc", + "Default": "Mặc Định", + "TaskOptimizeDatabaseDescription": "Thu gọn cơ sở dữ liệu và cắt bớt dung lượng trống. Chạy tác vụ này sau khi quét thư viện hoặc thực hiện các thay đổi khác ngụ ý sửa đổi cơ sở dữ liệu có thể cải thiện hiệu suất.", + "TaskOptimizeDatabase": "Tối ưu hóa cơ sở dữ liệu", + "TaskKeyframeExtractor": "Trích Xuất Khung Hình", + "TaskKeyframeExtractorDescription": "Trích xuất khung hình chính từ các tệp video để tạo danh sách phát HLS chính xác hơn. Tác vụ này có thể chạy trong một thời gian dài.", + "External": "Bên ngoài", + "HearingImpaired": "Khiếm Thính", + "TaskRefreshTrickplayImages": "Tạo Ảnh Xem Trước Trickplay", + "TaskRefreshTrickplayImagesDescription": "Tạo bản xem trước trịckplay cho video trong thư viện đã bật.", + "TaskCleanCollectionsAndPlaylists": "Dọn dẹp bộ sưu tập và danh sách phát", + "TaskCleanCollectionsAndPlaylistsDescription": "Xóa các mục khỏi bộ sưu tập và danh sách phát không còn tồn tại.", + "TaskAudioNormalization": "Chuẩn Hóa Âm Thanh", + "TaskAudioNormalizationDescription": "Quét tập tin để tìm dữ liệu chuẩn hóa âm thanh.", + "TaskDownloadMissingLyricsDescription": "Tải xuống lời cho bài hát", + "TaskDownloadMissingLyrics": "Tải xuống lời bị thiếu", + "TaskExtractMediaSegmentsDescription": "Trích xuất hoặc lấy các phân đoạn phương tiện từ các plugin hỗ trợ MediaSegment.", + "TaskMoveTrickplayImages": "Di chuyển vị trí hình ảnh Trickplay", + "TaskMoveTrickplayImagesDescription": "Di chuyển các tập tin trickplay hiện có theo cài đặt thư viện.", + "TaskExtractMediaSegments": "Quét Phân Đoạn Phương Tiện", + "CleanupUserDataTask": "Tác vụ dọn dẹp dữ liệu người dùng", + "CleanupUserDataTaskDescription": "Làm sạch tất cả dữ liệu người dùng (trạng thái xem, trạng thái yêu thích, v.v.) từ phương tiện không còn có mặt trong ít nhất 90 ngày." +} diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json new file mode 100644 index 00000000..0a0795d4 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -0,0 +1,141 @@ +{ + "Albums": "专辑", + "AppDeviceValues": "应用:{0},设备:{1}", + "Application": "应用程序", + "Artists": "艺术家", + "AuthenticationSucceededWithUserName": "{0} 认证成功", + "Books": "书籍", + "CameraImageUploadedFrom": "已从 {0} 上传新的相机照片", + "Channels": "频道", + "ChapterNameValue": "章节 {0}", + "Collections": "合集", + "DeviceOfflineWithName": "{0} 已断开连接", + "DeviceOnlineWithName": "{0} 已连接", + "FailedLoginAttemptWithUserName": "来自 {0} 的登录失败", + "Favorites": "收藏夹", + "Folders": "文件夹", + "Genres": "类型", + "HeaderAlbumArtists": "专辑艺术家", + "HeaderContinueWatching": "继续观看", + "HeaderFavoriteAlbums": "收藏的专辑", + "HeaderFavoriteArtists": "收藏的艺术家", + "HeaderFavoriteEpisodes": "收藏的剧集", + "HeaderFavoriteShows": "收藏的节目", + "HeaderFavoriteSongs": "收藏的歌曲", + "HeaderLiveTV": "电视直播", + "HeaderNextUp": "接下来播放", + "HeaderRecordingGroups": "录制组", + "HomeVideos": "家庭视频", + "Inherit": "继承", + "ItemAddedWithName": "{0} 已添加到媒体库", + "ItemRemovedWithName": "{0} 已从媒体库移除", + "LabelIpAddressValue": "IP 地址:{0}", + "LabelRunningTimeValue": "运行时间:{0}", + "Latest": "最新", + "MessageApplicationUpdated": "Jellyfin 服务器已更新", + "MessageApplicationUpdatedTo": "Jellyfin 服务器版本已更新到 {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "服务器配置 {0} 部分已更新", + "MessageServerConfigurationUpdated": "服务器配置已更新", + "MixedContent": "混合内容", + "Movies": "电影", + "Music": "音乐", + "MusicVideos": "MV", + "NameInstallFailed": "{0} 安装失败", + "NameSeasonNumber": "第 {0} 季", + "NameSeasonUnknown": "未知季", + "NewVersionIsAvailable": "Jellyfin 服务器有新版本可供下载。", + "NotificationOptionApplicationUpdateAvailable": "有可用的应用程序更新", + "NotificationOptionApplicationUpdateInstalled": "应用程序更新已安装", + "NotificationOptionAudioPlayback": "音频已开始播放", + "NotificationOptionAudioPlaybackStopped": "音频播放已停止", + "NotificationOptionCameraImageUploaded": "相机照片已上传", + "NotificationOptionInstallationFailed": "安装失败", + "NotificationOptionNewLibraryContent": "已添加新内容", + "NotificationOptionPluginError": "插件出错", + "NotificationOptionPluginInstalled": "插件已安装", + "NotificationOptionPluginUninstalled": "插件已卸载", + "NotificationOptionPluginUpdateInstalled": "插件已更新", + "NotificationOptionServerRestartRequired": "服务器需要重启", + "NotificationOptionTaskFailed": "计划任务失败", + "NotificationOptionUserLockedOut": "用户已被锁定", + "NotificationOptionVideoPlayback": "视频已开始播放", + "NotificationOptionVideoPlaybackStopped": "视频播放已停止", + "Photos": "照片", + "Playlists": "播放列表", + "Plugin": "插件", + "PluginInstalledWithName": "{0} 已安装", + "PluginUninstalledWithName": "{0} 已卸载", + "PluginUpdatedWithName": "{0} 已更新", + "ProviderValue": "提供商:{0}", + "ScheduledTaskFailedWithName": "{0} 已失败", + "ScheduledTaskStartedWithName": "{0} 已开始", + "ServerNameNeedsToBeRestarted": "{0} 需要重新启动", + "Shows": "节目", + "Songs": "歌曲", + "StartupEmbyServerIsLoading": "Jellyfin 服务器正在启动,请稍后再试。", + "SubtitleDownloadFailureFromForItem": "无法从 {0} 下载 {1} 的字幕", + "Sync": "同步", + "System": "系统", + "TvShows": "电视剧", + "User": "用户", + "UserCreatedWithName": "已创建用户 {0}", + "UserDeletedWithName": "已删除用户 {0}", + "UserDownloadingItemWithValues": "{0} 正在下载 {1}", + "UserLockedOutWithName": "用户 {0} 已被锁定", + "UserOfflineFromDevice": "{0} 已从 {1} 断开", + "UserOnlineFromDevice": "{0} 已在 {1} 上线", + "UserPasswordChangedWithName": "用户 {0} 的密码已更改", + "UserPolicyUpdatedWithName": "用户协议已更新为 {0}", + "UserStartedPlayingItemWithValues": "{0} 在 {2} 上开始播放 {1}", + "UserStoppedPlayingItemWithValues": "{0} 在 {2} 上停止播放 {1}", + "ValueHasBeenAddedToLibrary": "{0} 已添加至您的媒体库中", + "ValueSpecialEpisodeName": "特典 - {0}", + "VersionNumber": "版本 {0}", + "TaskUpdatePluginsDescription": "为已设置为自动更新的插件下载和安装更新。", + "TaskRefreshPeople": "刷新演职人员", + "TasksChannelsCategory": "互联网频道", + "TasksLibraryCategory": "媒体库", + "TaskDownloadMissingSubtitlesDescription": "根据元数据设置在互联网上搜索缺少的字幕。", + "TaskDownloadMissingSubtitles": "下载缺少的字幕", + "TaskRefreshChannelsDescription": "刷新互联网频道信息。", + "TaskRefreshChannels": "刷新频道", + "TaskCleanTranscodeDescription": "删除存在超过 1 天的转码文件。", + "TaskCleanTranscode": "清理转码目录", + "TaskUpdatePlugins": "更新插件", + "TaskRefreshPeopleDescription": "更新媒体库中演员和导演的元数据。", + "TaskCleanLogsDescription": "删除存在超过 {0} 天的的日志文件。", + "TaskCleanLogs": "清理日志目录", + "TaskRefreshLibraryDescription": "扫描你的媒体库以获取新文件并刷新元数据。", + "TaskRefreshLibrary": "扫描媒体库", + "TaskRefreshChapterImagesDescription": "为包含章节的视频提取缩略图。", + "TaskRefreshChapterImages": "提取章节图片", + "TaskCleanCacheDescription": "删除系统不再需要的缓存文件。", + "TaskCleanCache": "清理缓存目录", + "TasksApplicationCategory": "应用程序", + "TasksMaintenanceCategory": "维护", + "TaskCleanActivityLog": "清理程序日志", + "TaskCleanActivityLogDescription": "删除早于设置时间的活动日志条目。", + "Undefined": "未定义", + "Forced": "强制的", + "Default": "默认", + "TaskOptimizeDatabaseDescription": "压缩数据库并优化可用空间,在扫描库或执行其他数据库修改后运行此任务可能会提高性能。", + "TaskOptimizeDatabase": "优化数据库", + "TaskKeyframeExtractorDescription": "从视频文件中提取关键帧以创建更准确的 HLS 播放列表。这项任务可能需要很长时间。", + "TaskKeyframeExtractor": "关键帧提取", + "External": "外部", + "HearingImpaired": "听力障碍", + "TaskRefreshTrickplayImages": "生成进度条预览图", + "TaskRefreshTrickplayImagesDescription": "为启用的媒体库中的视频生成进度条预览图。", + "TaskCleanCollectionsAndPlaylists": "清理合集和播放列表", + "TaskCleanCollectionsAndPlaylistsDescription": "清理合集和播放列表中已不存在的项目。", + "TaskAudioNormalization": "音频标准化", + "TaskAudioNormalizationDescription": "扫描文件以寻找音频标准化数据。", + "TaskDownloadMissingLyrics": "下载缺失的歌词", + "TaskDownloadMissingLyricsDescription": "下载歌曲歌词", + "TaskMoveTrickplayImages": "迁移进度条预览图的存储位置", + "TaskExtractMediaSegments": "媒体分段扫描", + "TaskExtractMediaSegmentsDescription": "从支持 MediaSegment 的插件中提取或获取媒体分段。", + "TaskMoveTrickplayImagesDescription": "根据媒体库设置移动现有的进度条预览图文件。", + "CleanupUserDataTask": "用户数据清理任务", + "CleanupUserDataTaskDescription": "清理已被删除超过90天的媒体中的所有用户数据(观看状态、收藏夹状态等)。" +} diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json new file mode 100644 index 00000000..e57a0c5b --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json @@ -0,0 +1,141 @@ +{ + "Albums": "專輯", + "AppDeviceValues": "程式:{0},設備:{1}", + "Application": "應用程式", + "Artists": "藝人", + "AuthenticationSucceededWithUserName": "成功授權 {0}", + "Books": "書籍", + "CameraImageUploadedFrom": "{0} 成功上傳一張新照片", + "Channels": "頻道", + "ChapterNameValue": "第 {0} 章", + "Collections": "系列", + "DeviceOfflineWithName": "{0} 已中斷連接", + "DeviceOnlineWithName": "{0} 已連接", + "FailedLoginAttemptWithUserName": "{0} 登入失敗", + "Favorites": "我的最愛", + "Folders": "資料夾", + "Genres": "風格", + "HeaderAlbumArtists": "專輯歌手", + "HeaderContinueWatching": "繼續觀看", + "HeaderFavoriteAlbums": "最愛的專輯", + "HeaderFavoriteArtists": "最愛的藝人", + "HeaderFavoriteEpisodes": "最愛的劇集", + "HeaderFavoriteShows": "最愛的節目", + "HeaderFavoriteSongs": "最愛的歌曲", + "HeaderLiveTV": "電視直播", + "HeaderNextUp": "繼續觀看", + "HeaderRecordingGroups": "錄製組", + "HomeVideos": "家庭影片", + "Inherit": "繼承", + "ItemAddedWithName": "{0} 已被加入至媒體庫", + "ItemRemovedWithName": "{0} 已從媒體庫移除", + "LabelIpAddressValue": "IP 地址:{0}", + "LabelRunningTimeValue": "運作時間:{0}", + "Latest": "最新", + "MessageApplicationUpdated": "Jellyfin 已被更新", + "MessageApplicationUpdatedTo": "Jellyfin 已被更新至 {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 已被更新", + "MessageServerConfigurationUpdated": "已更新伺服器設定", + "MixedContent": "混合內容", + "Movies": "電影", + "Music": "音樂", + "MusicVideos": "MV", + "NameInstallFailed": "{0} 安裝失敗", + "NameSeasonNumber": "第 {0} 季", + "NameSeasonUnknown": "未知的季度", + "NewVersionIsAvailable": "有新版本的 Jellyfin 可供下載。", + "NotificationOptionApplicationUpdateAvailable": "有可用的更新", + "NotificationOptionApplicationUpdateInstalled": "完成更新應用程式", + "NotificationOptionAudioPlayback": "播放音訊", + "NotificationOptionAudioPlaybackStopped": "停止播放音訊", + "NotificationOptionCameraImageUploaded": "相片上傳", + "NotificationOptionInstallationFailed": "安裝失敗", + "NotificationOptionNewLibraryContent": "新增媒體", + "NotificationOptionPluginError": "插件錯誤", + "NotificationOptionPluginInstalled": "安裝插件", + "NotificationOptionPluginUninstalled": "解除安裝插件", + "NotificationOptionPluginUpdateInstalled": "完成更新插件", + "NotificationOptionServerRestartRequired": "伺服器需要重啟", + "NotificationOptionTaskFailed": "排程工作執行失敗", + "NotificationOptionUserLockedOut": "封鎖用戶", + "NotificationOptionVideoPlayback": "播放影片", + "NotificationOptionVideoPlaybackStopped": "停止播放影片", + "Photos": "相片", + "Playlists": "播放清單", + "Plugin": "插件", + "PluginInstalledWithName": "已安裝 {0}", + "PluginUninstalledWithName": "已移除 {0}", + "PluginUpdatedWithName": "已更新 {0}", + "ProviderValue": "提供者:{0}", + "ScheduledTaskFailedWithName": "{0} 執行失敗", + "ScheduledTaskStartedWithName": "開始執行 {0}", + "ServerNameNeedsToBeRestarted": "{0} 需要重啟", + "Shows": "節目", + "Songs": "歌曲", + "StartupEmbyServerIsLoading": "正在載入 Jellyfin,請稍後再試。", + "SubtitleDownloadFailureFromForItem": "無法從 {0} 下載 {1} 的字幕", + "Sync": "同步", + "System": "系統", + "TvShows": "電視節目", + "User": "用戶", + "UserCreatedWithName": "建立新用戶 {0}", + "UserDeletedWithName": "用戶 {0} 已被移除", + "UserDownloadingItemWithValues": "{0} 正在下載 {1}", + "UserLockedOutWithName": "用戶 {0} 已被封鎖", + "UserOfflineFromDevice": "{0} 終止了 {1} 的連接", + "UserOnlineFromDevice": "{0} 從 {1} 連線", + "UserPasswordChangedWithName": "{0} 的密碼已被更改", + "UserPolicyUpdatedWithName": "使用條款已更新為 {0}", + "UserStartedPlayingItemWithValues": "{0} 在 {2} 上播放 {1}", + "UserStoppedPlayingItemWithValues": "{0} 停止在 {2} 上播放 {1}", + "ValueHasBeenAddedToLibrary": "{0} 已被加入至你的媒體庫", + "ValueSpecialEpisodeName": "特典 - {0}", + "VersionNumber": "版本 {0}", + "TaskDownloadMissingSubtitles": "下載欠缺字幕", + "TaskUpdatePlugins": "更新插件", + "TasksApplicationCategory": "應用程式", + "TaskRefreshLibraryDescription": "掃描媒體庫以加入新增的檔案及重新載入元數據。", + "TasksMaintenanceCategory": "維護", + "TaskDownloadMissingSubtitlesDescription": "根據元數據中的設定,在網上搜尋欠缺的字幕。", + "TaskRefreshChannelsDescription": "重新載入網絡頻道的資訊。", + "TaskRefreshChannels": "重新載入頻道", + "TaskCleanTranscodeDescription": "刪除超過一天的轉碼檔案。", + "TaskCleanTranscode": "清理轉碼檔資料夾", + "TaskUpdatePluginsDescription": "下載並更新能夠被自動更新的插件。", + "TaskRefreshPeopleDescription": "更新你的媒體中有關的演員和導演的元數據。", + "TaskCleanLogsDescription": "刪除超過{0}天的紀錄檔。", + "TaskCleanLogs": "清理紀錄檔資料夾", + "TaskRefreshLibrary": "掃描媒體庫", + "TaskRefreshChapterImagesDescription": "為帶有章節的影片建立縮圖。", + "TaskRefreshChapterImages": "提取章節圖像", + "TaskCleanCacheDescription": "刪除系統不再需要的緩存文件。", + "TaskCleanCache": "清理緩存資料夾", + "TasksChannelsCategory": "網絡頻道", + "TasksLibraryCategory": "媒體庫", + "TaskRefreshPeople": "重新載入人物", + "TaskCleanActivityLog": "清理活動記錄", + "Undefined": "未定義", + "Forced": "強制", + "Default": "預設", + "TaskOptimizeDatabaseDescription": "壓縮數據庫及釋放可用空間。完成任何會修改數據庫的工作(例如掃描媒體庫)後,執行此工作或可提升伺服器速度。", + "TaskOptimizeDatabase": "最佳化數據庫", + "TaskCleanActivityLogDescription": "刪除早於設定時間的活動記錄。", + "TaskKeyframeExtractorDescription": "提取關鍵影格(Keyframe)以建立更準確的 HLS playlist。此工作可能需要使用較長時間來完成。", + "TaskKeyframeExtractor": "關鍵影格提取器", + "External": "外部", + "HearingImpaired": "聽力障礙", + "TaskRefreshTrickplayImages": "建立 Trickplay 圖像", + "TaskRefreshTrickplayImagesDescription": "為已啟用 Trickplay 的媒體庫內的影片建立 Trickplay 預覽圖。", + "TaskExtractMediaSegments": "掃描媒體分段資訊", + "TaskExtractMediaSegmentsDescription": "從允許MediaSegment 功能的插件中獲取媒體片段。", + "TaskDownloadMissingLyrics": "下載欠缺歌詞", + "TaskDownloadMissingLyricsDescription": "下載歌詞", + "TaskCleanCollectionsAndPlaylists": "整理媒體與播放清單", + "TaskAudioNormalization": "音訊同等化", + "TaskAudioNormalizationDescription": "掃描檔案裏的音訊同等化資料。", + "TaskCleanCollectionsAndPlaylistsDescription": "從資料庫及播放清單中移除已不存在的項目。", + "TaskMoveTrickplayImagesDescription": "根據媒體庫設定移動現有的 Trickplay 檔案。", + "TaskMoveTrickplayImages": "轉移 Trickplay 影像位置", + "CleanupUserDataTask": "用戶資料清理工作", + "CleanupUserDataTaskDescription": "從用戶數據中清除已經被刪除超過 90 日的媒體相關資料。" +} diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json new file mode 100644 index 00000000..b3bb9106 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -0,0 +1,141 @@ +{ + "Albums": "專輯", + "AppDeviceValues": "應用程式:{0},裝置:{1}", + "Application": "應用程式", + "Artists": "藝人", + "AuthenticationSucceededWithUserName": "成功授權 {0}", + "Books": "書籍", + "CameraImageUploadedFrom": "已從 {0} 成功上傳一張照片", + "Channels": "頻道", + "ChapterNameValue": "章節 {0}", + "Collections": "系列作", + "DeviceOfflineWithName": "{0} 已中斷連接", + "DeviceOnlineWithName": "{0} 已連接", + "FailedLoginAttemptWithUserName": "來自 {0} 的登入失敗嘗試", + "Favorites": "我的最愛", + "Folders": "資料夾", + "Genres": "風格", + "HeaderAlbumArtists": "專輯演出者", + "HeaderContinueWatching": "繼續觀看", + "HeaderFavoriteAlbums": "最愛專輯", + "HeaderFavoriteArtists": "最愛的藝人", + "HeaderFavoriteEpisodes": "最愛的劇集", + "HeaderFavoriteShows": "最愛的節目", + "HeaderFavoriteSongs": "最愛的歌曲", + "HeaderLiveTV": "電視直播", + "HeaderNextUp": "接下來", + "HomeVideos": "家庭影片", + "ItemAddedWithName": "{0} 已新增至媒體庫", + "ItemRemovedWithName": "{0} 已從媒體庫移除", + "LabelIpAddressValue": "IP 位址:{0}", + "LabelRunningTimeValue": "運行時間:{0}", + "Latest": "最新", + "MessageApplicationUpdated": "Jellyfin 伺服器已經更新", + "MessageApplicationUpdatedTo": "Jellyfin 伺服器已經更新至 {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 部分已經更新", + "MessageServerConfigurationUpdated": "伺服器設定已經更新", + "MixedContent": "混合內容", + "Movies": "電影", + "Music": "音樂", + "MusicVideos": "MV", + "NameInstallFailed": "{0} 安裝失敗", + "NameSeasonNumber": "第 {0} 季", + "NameSeasonUnknown": "未知季數", + "NewVersionIsAvailable": "新版本的 Jellyfin 伺服器已經可供下載。", + "NotificationOptionApplicationUpdateAvailable": "有可用的應用程式更新", + "NotificationOptionApplicationUpdateInstalled": "應用程式更新已安裝", + "NotificationOptionAudioPlayback": "音訊播放已開始", + "NotificationOptionAudioPlaybackStopped": "音訊播放已停止", + "NotificationOptionCameraImageUploaded": "相片已上傳", + "NotificationOptionInstallationFailed": "安裝失敗", + "NotificationOptionNewLibraryContent": "已新增新內容", + "NotificationOptionPluginError": "擴充功能錯誤", + "NotificationOptionPluginInstalled": "擴充功能已安裝", + "NotificationOptionPluginUninstalled": "擴充功能已移除", + "NotificationOptionPluginUpdateInstalled": "擴充功能已更新", + "NotificationOptionServerRestartRequired": "伺服器需要重新啟動", + "NotificationOptionTaskFailed": "擴充功能任務失敗", + "NotificationOptionUserLockedOut": "使用者已鎖定", + "NotificationOptionVideoPlayback": "影片播放已開始", + "NotificationOptionVideoPlaybackStopped": "影片播放已停止", + "Photos": "相片", + "Playlists": "播放清單", + "Plugin": "擴充功能", + "PluginInstalledWithName": "已安裝 {0}", + "PluginUninstalledWithName": "已移除 {0}", + "PluginUpdatedWithName": "已更新 {0}", + "ProviderValue": "提供者:{0}", + "ScheduledTaskFailedWithName": "排程任務 {0} 執行失敗", + "ScheduledTaskStartedWithName": "排程任務 {0} 已開始", + "ServerNameNeedsToBeRestarted": "伺服器 {0} 需要重新啟動", + "Shows": "節目", + "Songs": "歌曲", + "StartupEmbyServerIsLoading": "Jellyfin 伺服器載入中,請稍後再試。", + "Sync": "同步", + "System": "系統", + "TvShows": "電視節目", + "User": "使用者", + "UserCreatedWithName": "已建立使用者 {0}", + "UserDeletedWithName": "已刪除使用者 {0}", + "UserDownloadingItemWithValues": "使用者 {0} 正在下載 {1}", + "UserLockedOutWithName": "使用者 {0} 已被鎖定", + "UserOfflineFromDevice": "使用者 {0} 已從 {1} 斷線", + "UserOnlineFromDevice": "使用者 {0} 已從 {1} 連線", + "UserPasswordChangedWithName": "使用者 {0} 的密碼已變更", + "UserPolicyUpdatedWithName": "使用者權限已更新為 {0}", + "UserStartedPlayingItemWithValues": "{0} 正在 {2} 上播放 {1}", + "UserStoppedPlayingItemWithValues": "{0} 已在 {2} 上停止播放 {1}", + "ValueHasBeenAddedToLibrary": "{0} 已新增至您的媒體庫", + "ValueSpecialEpisodeName": "特輯 - {0}", + "VersionNumber": "版本 {0}", + "HeaderRecordingGroups": "錄製組", + "Inherit": "繼承", + "SubtitleDownloadFailureFromForItem": "無法從 {0} 下載 {1} 的字幕", + "TaskDownloadMissingSubtitlesDescription": "透過媒體資訊從網路上搜尋遺失的字幕。", + "TaskDownloadMissingSubtitles": "下載遺失的字幕", + "TaskRefreshChannels": "重新整理頻道", + "TaskUpdatePlugins": "更新擴充功能", + "TaskRefreshPeople": "更新人物", + "TaskCleanLogsDescription": "刪除超過 {0} 天的日誌文件。", + "TaskCleanLogs": "清空日誌資料夾", + "TaskRefreshLibraryDescription": "重新掃描媒體庫的新檔案並更新媒體資訊。", + "TaskRefreshLibrary": "重新掃描媒體庫", + "TaskRefreshChapterImages": "擷取章節圖片", + "TaskCleanCacheDescription": "刪除系統已不需要的快取。", + "TaskCleanCache": "清除快取資料夾", + "TasksLibraryCategory": "媒體庫", + "TaskRefreshChannelsDescription": "重新整理網路頻道資料。", + "TaskCleanTranscodeDescription": "刪除超過一天的轉檔。", + "TaskCleanTranscode": "清除轉檔資料夾", + "TaskUpdatePluginsDescription": "下載並更新已啟用自動更新的擴充功能。", + "TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的資訊。", + "TaskRefreshChapterImagesDescription": "為有章節的影片建立縮圖。", + "TasksChannelsCategory": "網路頻道", + "TasksApplicationCategory": "應用程式", + "TasksMaintenanceCategory": "維護", + "TaskCleanActivityLogDescription": "刪除超過所設時間的活動紀錄。", + "TaskCleanActivityLog": "清除活動紀錄", + "Undefined": "未定義", + "Forced": "強制", + "Default": "預設", + "TaskOptimizeDatabaseDescription": "縮小資料庫並釋放可用空間。在掃描資料庫或進行資料庫相關的更動後使用此功能會增加效能。", + "TaskOptimizeDatabase": "最佳化資料庫", + "TaskKeyframeExtractorDescription": "將關鍵幀從影片檔案提取出來並建立更精準的HLS播放清單。這可能需要很長時間。", + "TaskKeyframeExtractor": "關鍵幀提取器", + "External": "外部", + "HearingImpaired": "聽力障礙", + "TaskRefreshTrickplayImages": "生成快轉縮圖", + "TaskRefreshTrickplayImagesDescription": "為啟用快轉縮圖的媒體庫生成快轉縮圖。", + "TaskCleanCollectionsAndPlaylists": "清理系列作和播放清單", + "TaskCleanCollectionsAndPlaylistsDescription": "清理系列作品與播放清單中已不存在的項目。", + "TaskAudioNormalization": "音量標準化", + "TaskAudioNormalizationDescription": "掃描文件以找出音量標準化資料。", + "TaskDownloadMissingLyrics": "下載缺少的歌詞", + "TaskDownloadMissingLyricsDescription": "卡在歌曲歌詞", + "TaskExtractMediaSegments": "掃描媒體片段", + "TaskExtractMediaSegmentsDescription": "從使用媒體片段的擴充功能取得媒體片段。", + "TaskMoveTrickplayImages": "遷移快轉縮圖位置", + "TaskMoveTrickplayImagesDescription": "根據媒體庫的設定遷移快轉縮圖的檔案。", + "CleanupUserDataTask": "用戶資料清理工作", + "CleanupUserDataTaskDescription": "從用戶資料中清除已被刪除超過 90 天的媒體的相關資料。" +} diff --git a/Emby.Server.Implementations/Localization/Core/zu.json b/Emby.Server.Implementations/Localization/Core/zu.json new file mode 100644 index 00000000..aa056d44 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/zu.json @@ -0,0 +1,38 @@ +{ + "TasksApplicationCategory": "Ukusetshenziswa", + "TasksLibraryCategory": "Umtapo", + "TasksMaintenanceCategory": "Ukunakekela", + "User": "Umsebenzisi", + "Undefined": "Akuchaziwe", + "System": "Isistimu", + "Sync": "Vumelanisa", + "Songs": "Amaculo", + "Shows": "Izinhlelo", + "Plugin": "Isijobelelo", + "Playlists": "Izinhla Zokudlalayo", + "Photos": "Izithombe", + "Music": "Umculo", + "Movies": "Amamuvi", + "Latest": "lwakamuva", + "Inherit": "Ngefa", + "Forced": "Kuphoqiwe", + "Application": "Ukusetshenziswa", + "Genres": "Izinhlobo", + "Folders": "Izikhwama", + "Favorites": "Izintandokazi", + "Default": "Okumisiwe", + "Collections": "Amaqoqo", + "Channels": "Amashaneli", + "Books": "Izincwadi", + "Artists": "Abadlali", + "Albums": "Ama-albhamu", + "CameraImageUploadedFrom": "Kulandelayo lwesithonjana sekhamera selithunyelwe kusuka ku {0}", + "HeaderFavoriteArtists": "Abasethi Abathandekayo", + "HeaderFavoriteEpisodes": "Izilimi Ezithandekayo", + "HeaderFavoriteShows": "Izisho Ezithandekayo", + "External": "Kwezifungo", + "FailedLoginAttemptWithUserName": "Ukushayiswa kwesithombe sokungena okungekho {0}", + "HeaderContinueWatching": "Buyela Ukubona", + "HeaderFavoriteAlbums": "Izimpahla Ezithandwayo", + "HeaderAlbumArtists": "Abasethi wenkulumo" +} diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs new file mode 100644 index 00000000..bc80c2b4 --- /dev/null +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -0,0 +1,562 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.Json; +using System.Threading.Tasks; +using Jellyfin.Extensions; +using Jellyfin.Extensions.Json; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Localization +{ + /// <summary> + /// Class LocalizationManager. + /// </summary> + public class LocalizationManager : ILocalizationManager + { + private const string DefaultCulture = "en-US"; + private const string RatingsPath = "Emby.Server.Implementations.Localization.Ratings."; + private const string CulturesPath = "Emby.Server.Implementations.Localization.iso6392.txt"; + private const string CountriesPath = "Emby.Server.Implementations.Localization.countries.json"; + private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly; + private static readonly string[] _unratedValues = ["n/a", "unrated", "not rated", "nr"]; + + private readonly IServerConfigurationManager _configurationManager; + private readonly ILogger<LocalizationManager> _logger; + + private readonly Dictionary<string, Dictionary<string, ParentalRatingScore?>> _allParentalRatings = new(StringComparer.OrdinalIgnoreCase); + + private readonly ConcurrentDictionary<string, Dictionary<string, string>> _dictionaries = new(StringComparer.OrdinalIgnoreCase); + + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + + private readonly ConcurrentDictionary<string, CultureDto?> _cultureCache = new(StringComparer.OrdinalIgnoreCase); + private List<CultureDto> _cultures = []; + + private FrozenDictionary<string, string> _iso6392BtoT = null!; + + /// <summary> + /// Initializes a new instance of the <see cref="LocalizationManager" /> class. + /// </summary> + /// <param name="configurationManager">The configuration manager.</param> + /// <param name="logger">The logger.</param> + public LocalizationManager( + IServerConfigurationManager configurationManager, + ILogger<LocalizationManager> logger) + { + _configurationManager = configurationManager; + _logger = logger; + } + + /// <summary> + /// Loads all resources into memory. + /// </summary> + /// <returns><see cref="Task" />.</returns> + public async Task LoadAll() + { + // Extract from the assembly + foreach (var resource in _assembly.GetManifestResourceNames()) + { + if (!resource.StartsWith(RatingsPath, StringComparison.Ordinal)) + { + continue; + } + + using var stream = _assembly.GetManifestResourceStream(resource); + if (stream is not null) + { + var ratingSystem = await JsonSerializer.DeserializeAsync<ParentalRatingSystem>(stream, _jsonOptions).ConfigureAwait(false) + ?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'"); + + var dict = new Dictionary<string, ParentalRatingScore?>(); + if (ratingSystem.Ratings is not null) + { + foreach (var ratingEntry in ratingSystem.Ratings) + { + foreach (var ratingString in ratingEntry.RatingStrings) + { + dict[ratingString] = ratingEntry.RatingScore; + } + } + + _allParentalRatings[ratingSystem.CountryCode] = dict; + } + } + } + + await LoadCultures().ConfigureAwait(false); + } + + /// <summary> + /// Gets the cultures. + /// </summary> + /// <returns><see cref="IEnumerable{CultureDto}" />.</returns> + public IEnumerable<CultureDto> GetCultures() + => _cultures; + + private async Task LoadCultures() + { + List<CultureDto> list = []; + Dictionary<string, string> iso6392BtoTdict = new Dictionary<string, string>(); + + using var stream = _assembly.GetManifestResourceStream(CulturesPath); + if (stream is null) + { + throw new InvalidOperationException($"Invalid resource path: '{CulturesPath}'"); + } + else + { + using var reader = new StreamReader(stream); + await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) + { + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + var parts = line.Split('|'); + if (parts.Length != 5) + { + throw new InvalidDataException($"Invalid culture data found at: '{line}'"); + } + + string name = parts[3]; + string displayname = parts[3]; + if (string.IsNullOrWhiteSpace(displayname)) + { + continue; + } + + string twoCharName = parts[2]; + if (string.IsNullOrWhiteSpace(twoCharName)) + { + continue; + } + else if (twoCharName.Contains('-', StringComparison.OrdinalIgnoreCase)) + { + name = twoCharName; + } + + string[] threeLetterNames; + if (string.IsNullOrWhiteSpace(parts[1])) + { + threeLetterNames = [parts[0]]; + } + else + { + threeLetterNames = [parts[0], parts[1]]; + + // In cases where there are two TLN the first one is ISO 639-2/T and the second one is ISO 639-2/B + // We need ISO 639-2/T for the .NET cultures so we cultivate a dictionary for the translation B->T + iso6392BtoTdict.TryAdd(parts[1], parts[0]); + } + + list.Add(new CultureDto(name, displayname, twoCharName, threeLetterNames)); + } + + _cultureCache.Clear(); + _cultures = list; + _iso6392BtoT = iso6392BtoTdict.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase); + } + } + + /// <inheritdoc /> + public CultureDto? FindLanguageInfo(string language) + { + if (string.IsNullOrEmpty(language)) + { + return null; + } + + return _cultureCache.GetOrAdd( + language, + static (lang, cultures) => + { + // TODO language should ideally be a ReadOnlySpan but moq cannot mock ref structs + for (var i = 0; i < cultures.Count; i++) + { + var culture = cultures[i]; + if (lang.Equals(culture.DisplayName, StringComparison.OrdinalIgnoreCase) + || lang.Equals(culture.Name, StringComparison.OrdinalIgnoreCase) + || culture.ThreeLetterISOLanguageNames.Contains(lang, StringComparison.OrdinalIgnoreCase) + || lang.Equals(culture.TwoLetterISOLanguageName, StringComparison.OrdinalIgnoreCase)) + { + return culture; + } + } + + return null; + }, + _cultures); + } + + /// <inheritdoc /> + public IReadOnlyList<CountryInfo> GetCountries() + { + using var stream = _assembly.GetManifestResourceStream(CountriesPath) ?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'"); + + return JsonSerializer.Deserialize<IReadOnlyList<CountryInfo>>(stream, _jsonOptions) ?? []; + } + + /// <inheritdoc /> + public IReadOnlyList<ParentalRating> GetParentalRatings() + { + // Use server default language for ratings + // Fall back to empty list if there are no parental ratings for that language + var ratings = GetParentalRatingsDictionary()?.Select(x => new ParentalRating(x.Key, x.Value)).ToList() ?? []; + + // Add common ratings to ensure them being available for selection + // Based on the US rating system due to it being the main source of rating in the metadata providers + // Unrated + if (!ratings.Any(x => x is null)) + { + ratings.Add(new("Unrated", null)); + } + + // Minimum rating possible + if (ratings.All(x => x.RatingScore?.Score != 0)) + { + ratings.Add(new("Approved", new(0, null))); + } + + // Matches PG (this has different age restrictions depending on country) + if (ratings.All(x => x.RatingScore?.Score != 10)) + { + ratings.Add(new("10", new(10, null))); + } + + // Matches PG-13 + if (ratings.All(x => x.RatingScore?.Score != 13)) + { + ratings.Add(new("13", new(13, null))); + } + + // Matches TV-14 + if (ratings.All(x => x.RatingScore?.Score != 14)) + { + ratings.Add(new("14", new(14, null))); + } + + // Catchall if max rating of country is less than 21 + // Using 21 instead of 18 to be sure to allow access to all rated content except adult and banned + if (!ratings.Any(x => x.RatingScore?.Score >= 21)) + { + ratings.Add(new ParentalRating("21", new(21, null))); + } + + // A lot of countries don't explicitly have a separate rating for adult content + if (ratings.All(x => x.RatingScore?.Score != 1000)) + { + ratings.Add(new ParentalRating("XXX", new(1000, null))); + } + + // A lot of countries don't explicitly have a separate rating for banned content + if (ratings.All(x => x.RatingScore?.Score != 1001)) + { + ratings.Add(new ParentalRating("Banned", new(1001, null))); + } + + return [.. ratings.OrderBy(r => r.RatingScore?.Score).ThenBy(r => r.RatingScore?.SubScore)]; + } + + /// <summary> + /// Gets the parental ratings dictionary. + /// </summary> + /// <param name="countryCode">The optional two letter ISO language string.</param> + /// <returns><see cref="Dictionary{String, ParentalRatingScore}" />.</returns> + private Dictionary<string, ParentalRatingScore?>? GetParentalRatingsDictionary(string? countryCode = null) + { + // Fallback to server default if no country code is specified. + if (string.IsNullOrEmpty(countryCode)) + { + countryCode = _configurationManager.Configuration.MetadataCountryCode; + } + + if (_allParentalRatings.TryGetValue(countryCode, out var countryValue)) + { + return countryValue; + } + + return null; + } + + /// <inheritdoc /> + public ParentalRatingScore? GetRatingScore(string rating, string? countryCode = null) + { + ArgumentException.ThrowIfNullOrEmpty(rating); + + // Handle unrated content + if (_unratedValues.Contains(rating.AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + // Convert ints directly + // This may override some of the locale specific age ratings (but those always map to the same age) + if (int.TryParse(rating, out var ratingAge)) + { + return new(ratingAge, null); + } + + // Fairly common for some users to have "Rated R" in their rating field + rating = rating.Replace("Rated :", string.Empty, StringComparison.OrdinalIgnoreCase) + .Replace("Rated:", string.Empty, StringComparison.OrdinalIgnoreCase) + .Replace("Rated ", string.Empty, StringComparison.OrdinalIgnoreCase) + .Trim(); + + // Use rating system matching the language + if (!string.IsNullOrEmpty(countryCode)) + { + var ratingsDictionary = GetParentalRatingsDictionary(countryCode); + if (ratingsDictionary is not null && ratingsDictionary.TryGetValue(rating, out ParentalRatingScore? value)) + { + return value; + } + } + else + { + // Fall back to server default language for ratings check + var ratingsDictionary = GetParentalRatingsDictionary(); + if (ratingsDictionary is not null && ratingsDictionary.TryGetValue(rating, out ParentalRatingScore? value)) + { + return value; + } + } + + // If we don't find anything, check all ratings systems, starting with US + if (_allParentalRatings.TryGetValue("us", out var usRatings) && usRatings.TryGetValue(rating, out var usValue)) + { + return usValue; + } + + foreach (var dictionary in _allParentalRatings.Values) + { + if (dictionary.TryGetValue(rating, out var value)) + { + return value; + } + } + + // Try splitting by : to handle "Germany: FSK-18" + if (rating.Contains(':', StringComparison.OrdinalIgnoreCase)) + { + var ratingLevelRightPart = rating.AsSpan().RightPart(':'); + if (ratingLevelRightPart.Length != 0) + { + return GetRatingScore(ratingLevelRightPart.ToString()); + } + } + + // Handle prefix country code to handle "DE-18" + if (rating.Contains('-', StringComparison.OrdinalIgnoreCase)) + { + var ratingSpan = rating.AsSpan(); + + // Extract culture from country prefix + var culture = FindLanguageInfo(ratingSpan.LeftPart('-').ToString()); + + var ratingLevelRightPart = ratingSpan.RightPart('-'); + if (ratingLevelRightPart.Length != 0) + { + // Check rating system of culture + return GetRatingScore(ratingLevelRightPart.ToString(), culture?.TwoLetterISOLanguageName); + } + } + + return null; + } + + /// <inheritdoc /> + public string GetLocalizedString(string phrase) + { + return GetLocalizedString(phrase, _configurationManager.Configuration.UICulture); + } + + /// <inheritdoc /> + public string GetLocalizedString(string phrase, string culture) + { + if (string.IsNullOrEmpty(culture)) + { + culture = _configurationManager.Configuration.UICulture; + } + + if (string.IsNullOrEmpty(culture)) + { + culture = DefaultCulture; + } + + var dictionary = GetLocalizationDictionary(culture); + + if (dictionary.TryGetValue(phrase, out var value)) + { + return value; + } + + return phrase; + } + + private Dictionary<string, string> GetLocalizationDictionary(string culture) + { + ArgumentException.ThrowIfNullOrEmpty(culture); + + const string Prefix = "Core"; + + return _dictionaries.GetOrAdd( + culture, + static (key, localizationManager) => localizationManager.GetDictionary(Prefix, key, DefaultCulture + ".json").GetAwaiter().GetResult(), + this); + } + + private async Task<Dictionary<string, string>> GetDictionary(string prefix, string culture, string baseFilename) + { + ArgumentException.ThrowIfNullOrEmpty(culture); + + var dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + + var namespaceName = GetType().Namespace + "." + prefix; + + await CopyInto(dictionary, namespaceName + "." + baseFilename).ConfigureAwait(false); + await CopyInto(dictionary, namespaceName + "." + GetResourceFilename(culture)).ConfigureAwait(false); + + return dictionary; + } + + private async Task CopyInto(IDictionary<string, string> dictionary, string resourcePath) + { + using var stream = _assembly.GetManifestResourceStream(resourcePath); + // If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain + if (stream is null) + { + _logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath); + return; + } + + var dict = await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(stream, _jsonOptions).ConfigureAwait(false) ?? throw new InvalidOperationException($"Resource contains invalid data: '{stream}'"); + foreach (var key in dict.Keys) + { + dictionary[key] = dict[key]; + } + } + + private static string GetResourceFilename(string culture) + { + var parts = culture.Split('-'); + + if (parts.Length == 2) + { + culture = parts[0].ToLowerInvariant() + "-" + parts[1].ToUpperInvariant(); + } + else + { + culture = culture.ToLowerInvariant(); + } + + return culture + ".json"; + } + + /// <inheritdoc /> + public IEnumerable<LocalizationOption> GetLocalizationOptions() + { + yield return new LocalizationOption("Afrikaans", "af"); + yield return new LocalizationOption("العربية", "ar"); + yield return new LocalizationOption("Беларуская", "be"); + yield return new LocalizationOption("Български", "bg-BG"); + yield return new LocalizationOption("বাংলা (বাংলাদেশ)", "bn"); + yield return new LocalizationOption("Català", "ca"); + yield return new LocalizationOption("Čeština", "cs"); + yield return new LocalizationOption("Cymraeg", "cy"); + yield return new LocalizationOption("Dansk", "da"); + yield return new LocalizationOption("Deutsch", "de"); + yield return new LocalizationOption("English (United Kingdom)", "en-GB"); + yield return new LocalizationOption("English", "en-US"); + yield return new LocalizationOption("Ελληνικά", "el"); + yield return new LocalizationOption("Esperanto", "eo"); + yield return new LocalizationOption("Español", "es"); + yield return new LocalizationOption("Español americano", "es_419"); + yield return new LocalizationOption("Español (Argentina)", "es-AR"); + yield return new LocalizationOption("Español (Dominicana)", "es_DO"); + yield return new LocalizationOption("Español (México)", "es-MX"); + yield return new LocalizationOption("Eesti", "et"); + yield return new LocalizationOption("Basque", "eu"); + yield return new LocalizationOption("فارسی", "fa"); + yield return new LocalizationOption("Suomi", "fi"); + yield return new LocalizationOption("Filipino", "fil"); + yield return new LocalizationOption("Français", "fr"); + yield return new LocalizationOption("Français (Canada)", "fr-CA"); + yield return new LocalizationOption("Galego", "gl"); + yield return new LocalizationOption("Schwiizerdütsch", "gsw"); + yield return new LocalizationOption("עִבְרִית", "he"); + yield return new LocalizationOption("हिन्दी", "hi"); + yield return new LocalizationOption("Hrvatski", "hr"); + yield return new LocalizationOption("Magyar", "hu"); + yield return new LocalizationOption("Bahasa Indonesia", "id"); + yield return new LocalizationOption("Íslenska", "is"); + yield return new LocalizationOption("Italiano", "it"); + yield return new LocalizationOption("日本語", "ja"); + yield return new LocalizationOption("Qazaqşa", "kk"); + yield return new LocalizationOption("한국어", "ko"); + yield return new LocalizationOption("Lietuvių", "lt"); + yield return new LocalizationOption("Latviešu", "lv"); + yield return new LocalizationOption("Македонски", "mk"); + yield return new LocalizationOption("മലയാളം", "ml"); + yield return new LocalizationOption("मराठी", "mr"); + yield return new LocalizationOption("Bahasa Melayu", "ms"); + yield return new LocalizationOption("Norsk bokmål", "nb"); + yield return new LocalizationOption("नेपाली", "ne"); + yield return new LocalizationOption("Nederlands", "nl"); + yield return new LocalizationOption("Norsk nynorsk", "nn"); + yield return new LocalizationOption("ਪੰਜਾਬੀ", "pa"); + yield return new LocalizationOption("Polski", "pl"); + yield return new LocalizationOption("Pirate", "pr"); + yield return new LocalizationOption("Português", "pt"); + yield return new LocalizationOption("Português (Brasil)", "pt-BR"); + yield return new LocalizationOption("Português (Portugal)", "pt-PT"); + yield return new LocalizationOption("Românește", "ro"); + yield return new LocalizationOption("Русский", "ru"); + yield return new LocalizationOption("Slovenčina", "sk"); + yield return new LocalizationOption("Slovenščina", "sl-SI"); + yield return new LocalizationOption("Shqip", "sq"); + yield return new LocalizationOption("Српски", "sr"); + yield return new LocalizationOption("Svenska", "sv"); + yield return new LocalizationOption("தமிழ்", "ta"); + yield return new LocalizationOption("తెలుగు", "te"); + yield return new LocalizationOption("ภาษาไทย", "th"); + yield return new LocalizationOption("Türkçe", "tr"); + yield return new LocalizationOption("Українська", "uk"); + yield return new LocalizationOption("اُردُو", "ur_PK"); + yield return new LocalizationOption("Tiếng Việt", "vi"); + yield return new LocalizationOption("汉语 (简体字)", "zh-CN"); + yield return new LocalizationOption("漢語 (繁體字)", "zh-TW"); + yield return new LocalizationOption("廣東話 (香港)", "zh-HK"); + } + + /// <inheritdoc /> + public bool TryGetISO6392TFromB(string isoB, [NotNullWhen(true)] out string? isoT) + { + // Unlikely case the dictionary is not (yet) initialized properly + if (_iso6392BtoT is null) + { + isoT = null; + return false; + } + + var result = _iso6392BtoT.TryGetValue(isoB, out isoT) && !string.IsNullOrEmpty(isoT); + + // Ensure the ISO code being null if the result is false + if (!result) + { + isoT = null; + } + + return result; + } + } +} diff --git a/Emby.Server.Implementations/Localization/Ratings/0-prefer.json b/Emby.Server.Implementations/Localization/Ratings/0-prefer.json new file mode 100644 index 00000000..b3901516 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/0-prefer.json @@ -0,0 +1,34 @@ +{ + "countryCode": "0-prefer", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["E", "EC"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["T"], + "ratingScore": { + "score": 7, + "subScore": null + } + }, + { + "ratingStrings": ["M", "AO", "UR", "RP"], + "ratingScore": { + "score": 18, + "subScore": null + } + }, + { + "ratingStrings": ["X", "XX", "XXX", "XXXX"], + "ratingScore": { + "score": 1000, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/ar.json b/Emby.Server.Implementations/Localization/Ratings/ar.json new file mode 100644 index 00000000..73dfd2c7 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/ar.json @@ -0,0 +1,41 @@ +{ + "countryCode": "ar", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["ATP"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["+13"], + "ratingScore": { + "score": 13, + "subScore": null + } + }, + { + "ratingStrings": ["+16"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["+18"], + "ratingScore": { + "score": 18, + "subScore": null + } + }, + { + "ratingStrings": ["C"], + "ratingScore": { + "score": 1001, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/au.json b/Emby.Server.Implementations/Localization/Ratings/au.json new file mode 100644 index 00000000..a563df89 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/au.json @@ -0,0 +1,69 @@ +{ + "countryCode": "au", + "supportsSubScores": true, + "ratings": [ + { + "ratingStrings": ["Exempt", "G"], + "ratingScore": { + "score": 0, + "subScore": 0 + } + }, + { + "ratingStrings": ["7+"], + "ratingScore": { + "score": 7, + "subScore": 0 + } + }, + { + "ratingStrings": ["PG"], + "ratingScore": { + "score": 15, + "subScore": 1 + } + }, + { + "ratingStrings": ["M"], + "ratingScore": { + "score": 15, + "subScore": 2 + } + }, + { + "ratingStrings": ["MA", "MA 15+", "MA15+"], + "ratingScore": { + "score": 15, + "subScore": 3 + } + }, + { + "ratingStrings": ["16+"], + "ratingScore": { + "score": 16, + "subScore": 0 + } + }, + { + "ratingStrings": ["18+", "R", "R18+", "R 18+"], + "ratingScore": { + "score": 18, + "subScore": 1 + } + }, + { + "ratingStrings": ["X", "X18", "X 18"], + "ratingScore": { + "score": 1000, + "subScore": 0 + } + }, + { + "ratingStrings": ["RC"], + "ratingScore": { + "score": 1001, + "subScore": 0 + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/be.json b/Emby.Server.Implementations/Localization/Ratings/be.json new file mode 100644 index 00000000..18ea2c26 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/be.json @@ -0,0 +1,55 @@ +{ + "countryCode": "be", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["AL", "KT", "TOUS"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["6", "MG6"], + "ratingScore": { + "score": 6, + "subScore": null + } + }, + { + "ratingStrings": ["9"], + "ratingScore": { + "score": 9, + "subScore": null + } + }, + { + "ratingStrings": ["12", "KNT"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["14"], + "ratingScore": { + "score": 14, + "subScore": null + } + }, + { + "ratingStrings": ["16"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["18"], + "ratingScore": { + "score": 18, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/bg.json b/Emby.Server.Implementations/Localization/Ratings/bg.json new file mode 100644 index 00000000..fa03fa9d --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/bg.json @@ -0,0 +1,34 @@ +{ + "countryCode": "bg", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["A","B"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["C"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["D"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["X"], + "ratingScore": { + "score": 18, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/br.json b/Emby.Server.Implementations/Localization/Ratings/br.json new file mode 100644 index 00000000..f455b664 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/br.json @@ -0,0 +1,55 @@ +{ + "countryCode": "br", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["L", "AL", "Livre"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["9"], + "ratingScore": { + "score": 9, + "subScore": null + } + }, + { + "ratingStrings": ["10", "A10", "ER"], + "ratingScore": { + "score": 10, + "subScore": null + } + }, + { + "ratingStrings": ["12", "A12"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["14", "A14"], + "ratingScore": { + "score": 14, + "subScore": null + } + }, + { + "ratingStrings": ["16", "A16"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["18", "A18"], + "ratingScore": { + "score": 18, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/ca.json b/Emby.Server.Implementations/Localization/Ratings/ca.json new file mode 100644 index 00000000..fa43a8f2 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/ca.json @@ -0,0 +1,90 @@ +{ + "countryCode": "ca", + "supportsSubScores": true, + "ratings": [ + { + "ratingStrings": ["E", "G", "TV-Y", "TV-G"], + "ratingScore": { + "score": 0, + "subScore": 0 + } + }, + { + "ratingStrings": ["TV-Y7"], + "ratingScore": { + "score": 7, + "subScore": 0 + } + }, + { + "ratingStrings": ["TV-Y7-FV"], + "ratingScore": { + "score": 7, + "subScore": 1 + } + }, + { + "ratingStrings": ["PG", "TV-PG"], + "ratingScore": { + "score": 9, + "subScore": 0 + } + }, + { + "ratingStrings": ["14A"], + "ratingScore": { + "score": 14, + "subScore": 0 + } + }, + { + "ratingStrings": ["TV-14"], + "ratingScore": { + "score": 14, + "subScore": 1 + } + }, + { + "ratingStrings": ["16+"], + "ratingScore": { + "score": 16, + "subScore": 0 + } + }, + { + "ratingStrings": ["NC-17"], + "ratingScore": { + "score": 17, + "subScore": 0 + } + }, + { + "ratingStrings": ["18A"], + "ratingScore": { + "score": 18, + "subScore": 0 + } + }, + { + "ratingStrings": ["18+", "TV-MA", "R"], + "ratingScore": { + "score": 18, + "subScore": 1 + } + }, + { + "ratingStrings": ["A"], + "ratingScore": { + "score": 1000, + "subScore": 0 + } + }, + { + "ratingStrings": ["Prohibited"], + "ratingScore": { + "score": 1001, + "subScore": 0 + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/cl.json b/Emby.Server.Implementations/Localization/Ratings/cl.json new file mode 100644 index 00000000..08661947 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/cl.json @@ -0,0 +1,41 @@ +{ + "countryCode": "cl", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["TE"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["6"], + "ratingScore": { + "score": 6, + "subScore": null + } + }, + { + "ratingStrings": ["TE+7"], + "ratingScore": { + "score": 7, + "subScore": null + } + }, + { + "ratingStrings": ["14"], + "ratingScore": { + "score": 14, + "subScore": null + } + }, + { + "ratingStrings": ["18", "18V", "18S"], + "ratingScore": { + "score": 18, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/co.json b/Emby.Server.Implementations/Localization/Ratings/co.json new file mode 100644 index 00000000..4eff6dcc --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/co.json @@ -0,0 +1,55 @@ +{ + "countryCode": "co", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["T"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["7"], + "ratingScore": { + "score": 7, + "subScore": null + } + }, + { + "ratingStrings": ["12"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["15"], + "ratingScore": { + "score": 15, + "subScore": null + } + }, + { + "ratingStrings": ["18"], + "ratingScore": { + "score": 18, + "subScore": null + } + }, + { + "ratingStrings": ["X"], + "ratingScore": { + "score": 1000, + "subScore": null + } + }, + { + "ratingStrings": ["Prohibited"], + "ratingScore": { + "score": 1001, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/cz.json b/Emby.Server.Implementations/Localization/Ratings/cz.json new file mode 100644 index 00000000..92fff61a --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/cz.json @@ -0,0 +1,34 @@ +{ + "countryCode": "cz", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["U"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["12+"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["15+"], + "ratingScore": { + "score": 15, + "subScore": null + } + }, + { + "ratingStrings": ["18+"], + "ratingScore": { + "score": 18, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/de.json b/Emby.Server.Implementations/Localization/Ratings/de.json new file mode 100644 index 00000000..30c34b23 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/de.json @@ -0,0 +1,41 @@ +{ + "countryCode": "de", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["0", "FSK 0", "FSK-0", "Educational", "Infoprogramm"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["6", "FSK 6", "FSK-6"], + "ratingScore": { + "score": 6, + "subScore": null + } + }, + { + "ratingStrings": ["12", "FSK 12", "FSK-12"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["16", "FSK 16", "FSK-16"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["18", "FSK 18", "FSK-18"], + "ratingScore": { + "score": 18, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/dk.json b/Emby.Server.Implementations/Localization/Ratings/dk.json new file mode 100644 index 00000000..9fcd6d44 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/dk.json @@ -0,0 +1,48 @@ +{ + "countryCode": "dk", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["F", "A"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["7"], + "ratingScore": { + "score": 7, + "subScore": null + } + }, + { + "ratingStrings": ["11"], + "ratingScore": { + "score": 11, + "subScore": null + } + }, + { + "ratingStrings": ["12"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["15"], + "ratingScore": { + "score": 15, + "subScore": null + } + }, + { + "ratingStrings": ["16"], + "ratingScore": { + "score": 16, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/es.json b/Emby.Server.Implementations/Localization/Ratings/es.json new file mode 100644 index 00000000..961d64fe --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/es.json @@ -0,0 +1,90 @@ +{ + "countryCode": "es", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["0+", "A", "Ai","A/i", "A/fig", "A/i/fig", "APTA", "ERI", "TP"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["6+"], + "ratingScore": { + "score": 6, + "subScore": null + } + }, + { + "ratingStrings": ["7", "7i", "7/i", "7/fig", "7/i/fig"], + "ratingScore": { + "score": 11, + "subScore": null + } + }, + { + "ratingStrings": ["9+"], + "ratingScore": { + "score": 9, + "subScore": null + } + }, + { + "ratingStrings": ["10"], + "ratingScore": { + "score": 10, + "subScore": null + } + }, + { + "ratingStrings": ["12", "12/fig"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["13"], + "ratingScore": { + "score": 13, + "subScore": null + } + }, + { + "ratingStrings": ["14"], + "ratingScore": { + "score": 14, + "subScore": null + } + }, + { + "ratingStrings": ["16", "16/fig"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["18", "18/fig"], + "ratingScore": { + "score": 18, + "subScore": null + } + }, + { + "ratingStrings": ["X"], + "ratingScore": { + "score": 1000, + "subScore": null + } + }, + { + "ratingStrings": ["Banned"], + "ratingScore": { + "score": 1001, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/fi.json b/Emby.Server.Implementations/Localization/Ratings/fi.json new file mode 100644 index 00000000..0d55af65 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/fi.json @@ -0,0 +1,48 @@ +{ + "countryCode": "fi", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["S", "T"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["7", "K7", "K-7"], + "ratingScore": { + "score": 7, + "subScore": null + } + }, + { + "ratingStrings": ["12", "K12", "K-12"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["16", "K16", "K-16"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["18", "K18", "K-18"], + "ratingScore": { + "score": 18, + "subScore": null + } + }, + { + "ratingStrings": ["KK"], + "ratingScore": { + "score": 1001, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/fr.json b/Emby.Server.Implementations/Localization/Ratings/fr.json new file mode 100644 index 00000000..e8bafd6b --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/fr.json @@ -0,0 +1,69 @@ +{ + "countryCode": "fr", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["0+", "Public Averti", "Tous Publics", "TP", "U"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["6+"], + "ratingScore": { + "score": 6, + "subScore": null + } + }, + { + "ratingStrings": ["9+"], + "ratingScore": { + "score": 9, + "subScore": null + } + }, + { + "ratingStrings": ["10"], + "ratingScore": { + "score": 10, + "subScore": null + } + }, + { + "ratingStrings": ["12"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["14+"], + "ratingScore": { + "score": 14, + "subScore": null + } + }, + { + "ratingStrings": ["16"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["18"], + "ratingScore": { + "score": 18, + "subScore": null + } + }, + { + "ratingStrings": ["X"], + "ratingScore": { + "score": 1000, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/gb.json b/Emby.Server.Implementations/Localization/Ratings/gb.json new file mode 100644 index 00000000..7fc88272 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/gb.json @@ -0,0 +1,97 @@ +{ + "countryCode": "gb", + "supportsSubScores": true, + "ratings": [ + { + "ratingStrings": ["0+", "All", "E", "G", "U"], + "ratingScore": { + "score": 0, + "subScore": 0 + } + }, + { + "ratingStrings": ["6+"], + "ratingScore": { + "score": 6, + "subScore": 0 + } + }, + { + "ratingStrings": ["7+"], + "ratingScore": { + "score": 7, + "subScore": 0 + } + }, + { + "ratingStrings": ["PG"], + "ratingScore": { + "score": 8, + "subScore": 0 + } + }, + { + "ratingStrings": ["9"], + "ratingScore": { + "score": 9, + "subScore": 0 + } + }, + { + "ratingStrings": ["12A", "12PG"], + "ratingScore": { + "score": 12, + "subScore": 0 + } + }, + { + "ratingStrings": ["12", "12+"], + "ratingScore": { + "score": 12, + "subScore": 1 + } + }, + { + "ratingStrings": ["13+", "Teen"], + "ratingScore": { + "score": 13, + "subScore": 0 + } + }, + { + "ratingStrings": ["14+"], + "ratingScore": { + "score": 14, + "subScore": 0 + } + }, + { + "ratingStrings": ["15"], + "ratingScore": { + "score": 15, + "subScore": 3 + } + }, + { + "ratingStrings": ["16"], + "ratingScore": { + "score": 16, + "subScore": 0 + } + }, + { + "ratingStrings": ["18", "Caution"], + "ratingScore": { + "score": 18, + "subScore": 1 + } + }, + { + "ratingStrings": ["Mature", "Adult", "R18"], + "ratingScore": { + "score": 1000, + "subScore": 0 + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/gr.json b/Emby.Server.Implementations/Localization/Ratings/gr.json new file mode 100644 index 00000000..794bf0b3 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/gr.json @@ -0,0 +1,34 @@ +{ + "countryCode": "gr", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["K"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["K12"], + "ratingScore": { + "score": 13, + "subScore": null + } + }, + { + "ratingStrings": ["K15"], + "ratingScore": { + "score": 15, + "subScore": null + } + }, + { + "ratingStrings": ["K18"], + "ratingScore": { + "score": 18, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/hu.json b/Emby.Server.Implementations/Localization/Ratings/hu.json new file mode 100644 index 00000000..8043451e --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/hu.json @@ -0,0 +1,41 @@ +{ + "countryCode": "hu", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["KN"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["6"], + "ratingScore": { + "score": 6, + "subScore": null + } + }, + { + "ratingStrings": ["12"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["16"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["18", "X"], + "ratingScore": { + "score": 18, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/id.json b/Emby.Server.Implementations/Localization/Ratings/id.json new file mode 100644 index 00000000..8c687c23 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/id.json @@ -0,0 +1,34 @@ +{ + "countryCode": "id", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["SU"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["13+"], + "ratingScore": { + "score": 13, + "subScore": null + } + }, + { + "ratingStrings": ["17+"], + "ratingScore": { + "score": 17, + "subScore": null + } + }, + { + "ratingStrings": ["21+"], + "ratingScore": { + "score": 21, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/ie.json b/Emby.Server.Implementations/Localization/Ratings/ie.json new file mode 100644 index 00000000..f6cc56ed --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/ie.json @@ -0,0 +1,55 @@ +{ + "countryCode": "ie", + "supportsSubScores": true, + "ratings": [ + { + "ratingStrings": ["G"], + "ratingScore": { + "score": 4, + "subScore": 0 + } + }, + { + "ratingStrings": ["12A", "12PG", "PG"], + "ratingScore": { + "score": 12, + "subScore": 0 + } + }, + { + "ratingStrings": ["12"], + "ratingScore": { + "score": 12, + "subScore": 1 + } + }, + { + "ratingStrings": ["15A", "15PG"], + "ratingScore": { + "score": 15, + "subScore": 0 + } + }, + { + "ratingStrings": ["15"], + "ratingScore": { + "score": 15, + "subScore": 3 + } + }, + { + "ratingStrings": ["16"], + "ratingScore": { + "score": 16, + "subScore": 0 + } + }, + { + "ratingStrings": ["18"], + "ratingScore": { + "score": 18, + "subScore": 1 + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/in.json b/Emby.Server.Implementations/Localization/Ratings/in.json new file mode 100644 index 00000000..d6e6f80e --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/in.json @@ -0,0 +1,55 @@ +{ + "countryCode": "in", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["U"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["U/A 7+"], + "ratingScore": { + "score": 7, + "subScore": null + } + }, + { + "ratingStrings": ["UA"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["U/A 13+"], + "ratingScore": { + "score": 13, + "subScore": null + } + }, + { + "ratingStrings": ["U/A 16+"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["A"], + "ratingScore": { + "score": 18, + "subScore": null + } + }, + { + "ratingStrings": ["S"], + "ratingScore": { + "score": 1001, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/it.json b/Emby.Server.Implementations/Localization/Ratings/it.json new file mode 100644 index 00000000..f2889bf8 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/it.json @@ -0,0 +1,34 @@ +{ + "countryCode": "it", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["T"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["6+"], + "ratingScore": { + "score": 6, + "subScore": null + } + }, + { + "ratingStrings": ["14+"], + "ratingScore": { + "score": 14, + "subScore": null + } + }, + { + "ratingStrings": ["18+"], + "ratingScore": { + "score": 18, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/jp.json b/Emby.Server.Implementations/Localization/Ratings/jp.json new file mode 100644 index 00000000..efff9e92 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/jp.json @@ -0,0 +1,62 @@ +{ + "countryCode": "jp", + "supportsSubScores": true, + "ratings": [ + { + "ratingStrings": ["A", "G"], + "ratingScore": { + "score": 0, + "subScore": 0 + } + }, + { + "ratingStrings": ["PG12"], + "ratingScore": { + "score": 12, + "subScore": 0 + } + }, + { + "ratingStrings": ["B"], + "ratingScore": { + "score": 12, + "subScore": 1 + } + }, + { + "ratingStrings": ["15A", "15PG"], + "ratingScore": { + "score": 15, + "subScore": 0 + } + }, + { + "ratingStrings": ["C", "15+", "R15+"], + "ratingScore": { + "score": 15, + "subScore": 1 + } + }, + { + "ratingStrings": ["16+"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["D"], + "ratingScore": { + "score": 17, + "subScore": null + } + }, + { + "ratingStrings": ["18+", "Z"], + "ratingScore": { + "score": 18, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/kr.json b/Emby.Server.Implementations/Localization/Ratings/kr.json new file mode 100644 index 00000000..5c416a5e --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/kr.json @@ -0,0 +1,41 @@ +{ + "countryCode": "kr", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["ALL"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["12"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["15"], + "ratingScore": { + "score": 15, + "subScore": null + } + }, + { + "ratingStrings": ["19"], + "ratingScore": { + "score": 19, + "subScore": null + } + }, + { + "ratingStrings": ["Restricted Screening"], + "ratingScore": { + "score": 1001, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/kz.json b/Emby.Server.Implementations/Localization/Ratings/kz.json new file mode 100644 index 00000000..0f8f0c68 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/kz.json @@ -0,0 +1,41 @@ +{ + "countryCode": "kz", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["K"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["БА"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["Б14"], + "ratingScore": { + "score": 14, + "subScore": null + } + }, + { + "ratingStrings": ["E16"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["E18", "HA"], + "ratingScore": { + "score": 18, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/lt.json b/Emby.Server.Implementations/Localization/Ratings/lt.json new file mode 100644 index 00000000..c7b85a76 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/lt.json @@ -0,0 +1,41 @@ +{ + "countryCode": "lt", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["V"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["N-7"], + "ratingScore": { + "score": 7, + "subScore": null + } + }, + { + "ratingStrings": ["N-13"], + "ratingScore": { + "score": 13, + "subScore": null + } + }, + { + "ratingStrings": ["N-16"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["N-18"], + "ratingScore": { + "score": 18, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/mx.json b/Emby.Server.Implementations/Localization/Ratings/mx.json new file mode 100644 index 00000000..9dc3b89b --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/mx.json @@ -0,0 +1,41 @@ +{ + "countryCode": "mx", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["A", "AA"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["B"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["B-15"], + "ratingScore": { + "score": 15, + "subScore": null + } + }, + { + "ratingStrings": ["C"], + "ratingScore": { + "score": 18, + "subScore": null + } + }, + { + "ratingStrings": ["D"], + "ratingScore": { + "score": 1000, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/nl.json b/Emby.Server.Implementations/Localization/Ratings/nl.json new file mode 100644 index 00000000..2e43eb83 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/nl.json @@ -0,0 +1,55 @@ +{ + "countryCode": "nl", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["AL"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["6", "MG6"], + "ratingScore": { + "score": 6, + "subScore": null + } + }, + { + "ratingStrings": ["9"], + "ratingScore": { + "score": 9, + "subScore": null + } + }, + { + "ratingStrings": ["12"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["14"], + "ratingScore": { + "score": 14, + "subScore": null + } + }, + { + "ratingStrings": ["16"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["18"], + "ratingScore": { + "score": 18, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/no.json b/Emby.Server.Implementations/Localization/Ratings/no.json new file mode 100644 index 00000000..a5e95231 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/no.json @@ -0,0 +1,69 @@ +{ + "countryCode": "no", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["A"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["6"], + "ratingScore": { + "score": 6, + "subScore": null + } + }, + { + "ratingStrings": ["7"], + "ratingScore": { + "score": 7, + "subScore": null + } + }, + { + "ratingStrings": ["9"], + "ratingScore": { + "score": 9, + "subScore": null + } + }, + { + "ratingStrings": ["11"], + "ratingScore": { + "score": 11, + "subScore": null + } + }, + { + "ratingStrings": ["12"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["15"], + "ratingScore": { + "score": 15, + "subScore": null + } + }, + { + "ratingStrings": ["18"], + "ratingScore": { + "score": 18, + "subScore": null + } + }, + { + "ratingStrings": ["Not approved"], + "ratingScore": { + "score": 1001, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/nz.json b/Emby.Server.Implementations/Localization/Ratings/nz.json new file mode 100644 index 00000000..23b23c8c --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/nz.json @@ -0,0 +1,76 @@ +{ + "countryCode": "nz", + "supportsSubScores": true, + "ratings": [ + { + "ratingStrings": ["Exempt", "G"], + "ratingScore": { + "score": 0, + "subScore": 0 + } + }, + { + "ratingStrings": ["RP13", "PG"], + "ratingScore": { + "score": 13, + "subScore": 0 + } + }, + { + "ratingStrings": ["GY", "R13"], + "ratingScore": { + "score": 13, + "subScore": 1 + } + }, + { + "ratingStrings": ["R15"], + "ratingScore": { + "score": 15, + "subScore": 0 + } + }, + { + "ratingStrings": ["RP16", "M"], + "ratingScore": { + "score": 16, + "subScore": 0 + } + }, + { + "ratingStrings": ["R16"], + "ratingScore": { + "score": 16, + "subScore": 1 + } + }, + { + "ratingStrings": ["RP18"], + "ratingScore": { + "score": 18, + "subScore": 0 + } + }, + { + "ratingStrings": ["R18", "GA"], + "ratingScore": { + "score": 18, + "subScore": 1 + } + }, + { + "ratingStrings": ["MA"], + "ratingScore": { + "score": 1000, + "subScore": 0 + } + }, + { + "ratingStrings": ["Objectionable", "R"], + "ratingScore": { + "score": 1001, + "subScore": 0 + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/ph.json b/Emby.Server.Implementations/Localization/Ratings/ph.json new file mode 100644 index 00000000..0bce9df8 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/ph.json @@ -0,0 +1,48 @@ +{ + "countryCode": "ph", + "supportsSubScores": true, + "ratings": [ + { + "ratingStrings": ["G"], + "ratingScore": { + "score": 0, + "subScore": 0 + } + }, + { + "ratingStrings": ["PG"], + "ratingScore": { + "score": 13, + "subScore": 0 + } + }, + { + "ratingStrings": ["R-13"], + "ratingScore": { + "score": 13, + "subScore": 1 + } + }, + { + "ratingStrings": ["R-16"], + "ratingScore": { + "score": 16, + "subScore": 0 + } + }, + { + "ratingStrings": ["R-18"], + "ratingScore": { + "score": 18, + "subScore": 0 + } + }, + { + "ratingStrings": ["X"], + "ratingScore": { + "score": 1001, + "subScore": 0 + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/pl.json b/Emby.Server.Implementations/Localization/Ratings/pl.json new file mode 100644 index 00000000..97dddf3c --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/pl.json @@ -0,0 +1,41 @@ +{ + "countryCode": "pl", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["b.o.", "AL"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["7", "od 7 lat"], + "ratingScore": { + "score": 7, + "subScore": null + } + }, + { + "ratingStrings": ["12", "od 12 lat"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["16", "od 16 lat"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["18", "od 18 lat", "R"], + "ratingScore": { + "score": 18, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/pt.json b/Emby.Server.Implementations/Localization/Ratings/pt.json new file mode 100644 index 00000000..2ab796c8 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/pt.json @@ -0,0 +1,62 @@ +{ + "countryCode": "pt", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["Públicos"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["M/3"], + "ratingScore": { + "score": 3, + "subScore": null + } + }, + { + "ratingStrings": ["M/6"], + "ratingScore": { + "score": 6, + "subScore": null + } + }, + { + "ratingStrings": ["M/12"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["M/14"], + "ratingScore": { + "score": 14, + "subScore": null + } + }, + { + "ratingStrings": ["M/16"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["M/18"], + "ratingScore": { + "score": 18, + "subScore": null + } + }, + { + "ratingStrings": ["P"], + "ratingScore": { + "score": 1000, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/ro.json b/Emby.Server.Implementations/Localization/Ratings/ro.json new file mode 100644 index 00000000..aa6f7fe5 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/ro.json @@ -0,0 +1,48 @@ +{ + "countryCode": "ro", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["AG", "AP"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["12", "AP-12"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["15", "N-15"], + "ratingScore": { + "score": 15, + "subScore": null + } + }, + { + "ratingStrings": ["18", "IM-18"], + "ratingScore": { + "score": 18, + "subScore": null + } + }, + { + "ratingStrings": ["18+", "IM-18-XXX"], + "ratingScore": { + "score": 1000, + "subScore": null + } + }, + { + "ratingStrings": ["IC"], + "ratingScore": { + "score": 1001, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/ru.json b/Emby.Server.Implementations/Localization/Ratings/ru.json new file mode 100644 index 00000000..d1b8b13a --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/ru.json @@ -0,0 +1,48 @@ +{ + "countryCode": "ru", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["0+"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["6+"], + "ratingScore": { + "score": 6, + "subScore": null + } + }, + { + "ratingStrings": ["12+"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["16+"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["18+"], + "ratingScore": { + "score": 18, + "subScore": null + } + }, + { + "ratingStrings": ["Refused classification"], + "ratingScore": { + "score": 1001, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/se.json b/Emby.Server.Implementations/Localization/Ratings/se.json new file mode 100644 index 00000000..70084995 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/se.json @@ -0,0 +1,55 @@ +{ + "countryCode": "se", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["0+", "Alla", "Barntillåten", "Btl"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["7"], + "ratingScore": { + "score": 7, + "subScore": null + } + }, + { + "ratingStrings": ["9+"], + "ratingScore": { + "score": 9, + "subScore": null + } + }, + { + "ratingStrings": ["10+"], + "ratingScore": { + "score": 10, + "subScore": null + } + }, + { + "ratingStrings": ["11"], + "ratingScore": { + "score": 11, + "subScore": null + } + }, + { + "ratingStrings": ["14"], + "ratingScore": { + "score": 14, + "subScore": null + } + }, + { + "ratingStrings": ["15"], + "ratingScore": { + "score": 15, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/sg.json b/Emby.Server.Implementations/Localization/Ratings/sg.json new file mode 100644 index 00000000..47d9e283 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/sg.json @@ -0,0 +1,48 @@ +{ + "countryCode": "sg", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["G"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["PG"], + "ratingScore": { + "score": 7, + "subScore": null + } + }, + { + "ratingStrings": ["PG13"], + "ratingScore": { + "score": 13, + "subScore": null + } + }, + { + "ratingStrings": ["NC16"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["M18"], + "ratingScore": { + "score": 18, + "subScore": null + } + }, + { + "ratingStrings": ["R21"], + "ratingScore": { + "score": 21, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/sk.json b/Emby.Server.Implementations/Localization/Ratings/sk.json new file mode 100644 index 00000000..5ec6111e --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/sk.json @@ -0,0 +1,41 @@ +{ + "countryCode": "sk", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["U", "NR"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["7"], + "ratingScore": { + "score": 7, + "subScore": null + } + }, + { + "ratingStrings": ["12"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["15"], + "ratingScore": { + "score": 15, + "subScore": null + } + }, + { + "ratingStrings": ["18"], + "ratingScore": { + "score": 18, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/th.json b/Emby.Server.Implementations/Localization/Ratings/th.json new file mode 100644 index 00000000..44bfab21 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/th.json @@ -0,0 +1,48 @@ +{ + "countryCode": "th", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["P", "G"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["13"], + "ratingScore": { + "score": 13, + "subScore": null + } + }, + { + "ratingStrings": ["15"], + "ratingScore": { + "score": 15, + "subScore": null + } + }, + { + "ratingStrings": ["18+"], + "ratingScore": { + "score": 18, + "subScore": null + } + }, + { + "ratingStrings": ["20"], + "ratingScore": { + "score": 20, + "subScore": null + } + }, + { + "ratingStrings": ["Banned"], + "ratingScore": { + "score": 1001, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/tr.json b/Emby.Server.Implementations/Localization/Ratings/tr.json new file mode 100644 index 00000000..5a386885 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/tr.json @@ -0,0 +1,69 @@ +{ + "countryCode": "tr", + "supportsSubScores": true, + "ratings": [ + { + "ratingStrings": ["Genel İzleyici Kitlesi"], + "ratingScore": { + "score": 0, + "subScore": 0 + } + }, + { + "ratingStrings": ["6A"], + "ratingScore": { + "score": 6, + "subScore": 0 + } + }, + { + "ratingStrings": ["6+"], + "ratingScore": { + "score": 6, + "subScore": 1 + } + }, + { + "ratingStrings": ["10A"], + "ratingScore": { + "score": 10, + "subScore": 0 + } + }, + { + "ratingStrings": ["10+"], + "ratingScore": { + "score": 10, + "subScore": 1 + } + }, + { + "ratingStrings": ["13A"], + "ratingScore": { + "score": 13, + "subScore": 0 + } + }, + { + "ratingStrings": ["13+"], + "ratingScore": { + "score": 13, + "subScore": 1 + } + }, + { + "ratingStrings": ["16+"], + "ratingScore": { + "score": 16, + "subScore": 0 + } + }, + { + "ratingStrings": ["18+"], + "ratingScore": { + "score": 18, + "subScore": 0 + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/tw.json b/Emby.Server.Implementations/Localization/Ratings/tw.json new file mode 100644 index 00000000..a7869c12 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/tw.json @@ -0,0 +1,41 @@ +{ + "countryCode": "tw", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["0+"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["6+"], + "ratingScore": { + "score": 6, + "subScore": null + } + }, + { + "ratingStrings": ["12+"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["15+"], + "ratingScore": { + "score": 15, + "subScore": null + } + }, + { + "ratingStrings": ["18+"], + "ratingScore": { + "score": 18, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/ua.json b/Emby.Server.Implementations/Localization/Ratings/ua.json new file mode 100644 index 00000000..d8fe9516 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/ua.json @@ -0,0 +1,34 @@ +{ + "countryCode": "ua", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["0+"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["12+"], + "ratingScore": { + "score": 12, + "subScore": null + } + }, + { + "ratingStrings": ["16+"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["18+"], + "ratingScore": { + "score": 18, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/uk.json b/Emby.Server.Implementations/Localization/Ratings/uk.json new file mode 100644 index 00000000..7fc88272 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/uk.json @@ -0,0 +1,97 @@ +{ + "countryCode": "gb", + "supportsSubScores": true, + "ratings": [ + { + "ratingStrings": ["0+", "All", "E", "G", "U"], + "ratingScore": { + "score": 0, + "subScore": 0 + } + }, + { + "ratingStrings": ["6+"], + "ratingScore": { + "score": 6, + "subScore": 0 + } + }, + { + "ratingStrings": ["7+"], + "ratingScore": { + "score": 7, + "subScore": 0 + } + }, + { + "ratingStrings": ["PG"], + "ratingScore": { + "score": 8, + "subScore": 0 + } + }, + { + "ratingStrings": ["9"], + "ratingScore": { + "score": 9, + "subScore": 0 + } + }, + { + "ratingStrings": ["12A", "12PG"], + "ratingScore": { + "score": 12, + "subScore": 0 + } + }, + { + "ratingStrings": ["12", "12+"], + "ratingScore": { + "score": 12, + "subScore": 1 + } + }, + { + "ratingStrings": ["13+", "Teen"], + "ratingScore": { + "score": 13, + "subScore": 0 + } + }, + { + "ratingStrings": ["14+"], + "ratingScore": { + "score": 14, + "subScore": 0 + } + }, + { + "ratingStrings": ["15"], + "ratingScore": { + "score": 15, + "subScore": 3 + } + }, + { + "ratingStrings": ["16"], + "ratingScore": { + "score": 16, + "subScore": 0 + } + }, + { + "ratingStrings": ["18", "Caution"], + "ratingScore": { + "score": 18, + "subScore": 1 + } + }, + { + "ratingStrings": ["Mature", "Adult", "R18"], + "ratingScore": { + "score": 1000, + "subScore": 0 + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/us.json b/Emby.Server.Implementations/Localization/Ratings/us.json new file mode 100644 index 00000000..08a63731 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/us.json @@ -0,0 +1,83 @@ +{ + "countryCode": "us", + "supportsSubScores": true, + "ratings": [ + { + "ratingStrings": ["Approved", "G", "TV-G", "TV-Y"], + "ratingScore": { + "score": 0, + "subScore": 0 + } + }, + { + "ratingStrings": ["TV-Y7"], + "ratingScore": { + "score": 7, + "subScore": 0 + } + }, + { + "ratingStrings": ["TV-Y7-FV"], + "ratingScore": { + "score": 7, + "subScore": 1 + } + }, + { + "ratingStrings": ["PG", "TV-PG"], + "ratingScore": { + "score": 10, + "subScore": 0 + } + }, + { + "ratingStrings": ["TV-PG-D", "TV-PG-L", "TV-PG-S", "TV-PG-V", "TV-PG-DL", "TV-PG-DS", "TV-PG-DV", "TV-PG-LS", "TV-PG-LV", "TV-PG-SV", "TV-PG-DLS", "TV-PG-DLV", "TV-PG-DSV", "TV-PG-LSV", "TV-PG-DLSV"], + "ratingScore": { + "score": 10, + "subScore": 1 + } + }, + { + "ratingStrings": ["PG-13"], + "ratingScore": { + "score": 13, + "subScore": 0 + } + }, + { + "ratingStrings": ["TV-14"], + "ratingScore": { + "score": 14, + "subScore": 0 + } + }, + { + "ratingStrings": ["TV-14-D", "TV-14-L", "TV-14-S", "TV-14-V", "TV-14-DL", "TV-14-DS", "TV-14-DV", "TV-14-LS", "TV-14-LV", "TV-14-SV", "TV-14-DLS", "TV-14-DLV", "TV-14-DSV", "TV-14-LSV", "TV-14-DLSV"], + "ratingScore": { + "score": 14, + "subScore": 1 + } + }, + { + "ratingStrings": ["R"], + "ratingScore": { + "score": 17, + "subScore": 0 + } + }, + { + "ratingStrings": ["NC-17", "TV-MA", "TV-MA-L", "TV-MA-S", "TV-MA-V", "TV-MA-LS", "TV-MA-LV", "TV-MA-SV", "TV-MA-LSV"], + "ratingScore": { + "score": 17, + "subScore": 1 + } + }, + { + "ratingStrings": ["TV-X", "TV-AO"], + "ratingScore": { + "score": 18, + "subScore": 0 + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/Ratings/za.json b/Emby.Server.Implementations/Localization/Ratings/za.json new file mode 100644 index 00000000..fe13af79 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/za.json @@ -0,0 +1,55 @@ +{ + "countryCode": "za", + "supportsSubScores": false, + "ratings": [ + { + "ratingStrings": ["A"], + "ratingScore": { + "score": 0, + "subScore": null + } + }, + { + "ratingStrings": ["PG", "7-9PG"], + "ratingScore": { + "score": 7, + "subScore": null + } + }, + { + "ratingStrings": ["10-12PG"], + "ratingScore": { + "score": 10, + "subScore": null + } + }, + { + "ratingStrings": ["13"], + "ratingScore": { + "score": 13, + "subScore": null + } + }, + { + "ratingStrings": ["16"], + "ratingScore": { + "score": 16, + "subScore": null + } + }, + { + "ratingStrings": ["18"], + "ratingScore": { + "score": 18, + "subScore": null + } + }, + { + "ratingStrings": ["X18", "XX"], + "ratingScore": { + "score": 1001, + "subScore": null + } + } + ] +} diff --git a/Emby.Server.Implementations/Localization/countries.json b/Emby.Server.Implementations/Localization/countries.json new file mode 100644 index 00000000..d92dc880 --- /dev/null +++ b/Emby.Server.Implementations/Localization/countries.json @@ -0,0 +1,836 @@ +[ + { + "DisplayName": "Afghanistan", + "Name": "AF", + "ThreeLetterISORegionName": "AFG", + "TwoLetterISORegionName": "AF" + }, + { + "DisplayName": "Albania", + "Name": "AL", + "ThreeLetterISORegionName": "ALB", + "TwoLetterISORegionName": "AL" + }, + { + "DisplayName": "Algeria", + "Name": "DZ", + "ThreeLetterISORegionName": "DZA", + "TwoLetterISORegionName": "DZ" + }, + { + "DisplayName": "Argentina", + "Name": "AR", + "ThreeLetterISORegionName": "ARG", + "TwoLetterISORegionName": "AR" + }, + { + "DisplayName": "Armenia", + "Name": "AM", + "ThreeLetterISORegionName": "ARM", + "TwoLetterISORegionName": "AM" + }, + { + "DisplayName": "Australia", + "Name": "AU", + "ThreeLetterISORegionName": "AUS", + "TwoLetterISORegionName": "AU" + }, + { + "DisplayName": "Austria", + "Name": "AT", + "ThreeLetterISORegionName": "AUT", + "TwoLetterISORegionName": "AT" + }, + { + "DisplayName": "Azerbaijan", + "Name": "AZ", + "ThreeLetterISORegionName": "AZE", + "TwoLetterISORegionName": "AZ" + }, + { + "DisplayName": "Bahrain", + "Name": "BH", + "ThreeLetterISORegionName": "BHR", + "TwoLetterISORegionName": "BH" + }, + { + "DisplayName": "Bangladesh", + "Name": "BD", + "ThreeLetterISORegionName": "BGD", + "TwoLetterISORegionName": "BD" + }, + { + "DisplayName": "Belarus", + "Name": "BY", + "ThreeLetterISORegionName": "BLR", + "TwoLetterISORegionName": "BY" + }, + { + "DisplayName": "Belgium", + "Name": "BE", + "ThreeLetterISORegionName": "BEL", + "TwoLetterISORegionName": "BE" + }, + { + "DisplayName": "Belize", + "Name": "BZ", + "ThreeLetterISORegionName": "BLZ", + "TwoLetterISORegionName": "BZ" + }, + { + "DisplayName": "Bolivarian Republic of Venezuela", + "Name": "VE", + "ThreeLetterISORegionName": "VEN", + "TwoLetterISORegionName": "VE" + }, + { + "DisplayName": "Bolivia", + "Name": "BO", + "ThreeLetterISORegionName": "BOL", + "TwoLetterISORegionName": "BO" + }, + { + "DisplayName": "Bosnia and Herzegovina", + "Name": "BA", + "ThreeLetterISORegionName": "BIH", + "TwoLetterISORegionName": "BA" + }, + { + "DisplayName": "Botswana", + "Name": "BW", + "ThreeLetterISORegionName": "BWA", + "TwoLetterISORegionName": "BW" + }, + { + "DisplayName": "Brazil", + "Name": "BR", + "ThreeLetterISORegionName": "BRA", + "TwoLetterISORegionName": "BR" + }, + { + "DisplayName": "Brunei Darussalam", + "Name": "BN", + "ThreeLetterISORegionName": "BRN", + "TwoLetterISORegionName": "BN" + }, + { + "DisplayName": "Bulgaria", + "Name": "BG", + "ThreeLetterISORegionName": "BGR", + "TwoLetterISORegionName": "BG" + }, + { + "DisplayName": "Cambodia", + "Name": "KH", + "ThreeLetterISORegionName": "KHM", + "TwoLetterISORegionName": "KH" + }, + { + "DisplayName": "Cameroon", + "Name": "CM", + "ThreeLetterISORegionName": "CMR", + "TwoLetterISORegionName": "CM" + }, + { + "DisplayName": "Canada", + "Name": "CA", + "ThreeLetterISORegionName": "CAN", + "TwoLetterISORegionName": "CA" + }, + { + "DisplayName": "Caribbean", + "Name": "029", + "ThreeLetterISORegionName": "029", + "TwoLetterISORegionName": "029" + }, + { + "DisplayName": "Chile", + "Name": "CL", + "ThreeLetterISORegionName": "CHL", + "TwoLetterISORegionName": "CL" + }, + { + "DisplayName": "Colombia", + "Name": "CO", + "ThreeLetterISORegionName": "COL", + "TwoLetterISORegionName": "CO" + }, + { + "DisplayName": "Congo [DRC]", + "Name": "CD", + "ThreeLetterISORegionName": "COD", + "TwoLetterISORegionName": "CD" + }, + { + "DisplayName": "Costa Rica", + "Name": "CR", + "ThreeLetterISORegionName": "CRI", + "TwoLetterISORegionName": "CR" + }, + { + "DisplayName": "Croatia", + "Name": "HR", + "ThreeLetterISORegionName": "HRV", + "TwoLetterISORegionName": "HR" + }, + { + "DisplayName": "Czech Republic", + "Name": "CZ", + "ThreeLetterISORegionName": "CZE", + "TwoLetterISORegionName": "CZ" + }, + { + "DisplayName": "Denmark", + "Name": "DK", + "ThreeLetterISORegionName": "DNK", + "TwoLetterISORegionName": "DK" + }, + { + "DisplayName": "Dominican Republic", + "Name": "DO", + "ThreeLetterISORegionName": "DOM", + "TwoLetterISORegionName": "DO" + }, + { + "DisplayName": "Ecuador", + "Name": "EC", + "ThreeLetterISORegionName": "ECU", + "TwoLetterISORegionName": "EC" + }, + { + "DisplayName": "Egypt", + "Name": "EG", + "ThreeLetterISORegionName": "EGY", + "TwoLetterISORegionName": "EG" + }, + { + "DisplayName": "El Salvador", + "Name": "SV", + "ThreeLetterISORegionName": "SLV", + "TwoLetterISORegionName": "SV" + }, + { + "DisplayName": "Eritrea", + "Name": "ER", + "ThreeLetterISORegionName": "ERI", + "TwoLetterISORegionName": "ER" + }, + { + "DisplayName": "Estonia", + "Name": "EE", + "ThreeLetterISORegionName": "EST", + "TwoLetterISORegionName": "EE" + }, + { + "DisplayName": "Ethiopia", + "Name": "ET", + "ThreeLetterISORegionName": "ETH", + "TwoLetterISORegionName": "ET" + }, + { + "DisplayName": "Faroe Islands", + "Name": "FO", + "ThreeLetterISORegionName": "FRO", + "TwoLetterISORegionName": "FO" + }, + { + "DisplayName": "Finland", + "Name": "FI", + "ThreeLetterISORegionName": "FIN", + "TwoLetterISORegionName": "FI" + }, + { + "DisplayName": "France", + "Name": "FR", + "ThreeLetterISORegionName": "FRA", + "TwoLetterISORegionName": "FR" + }, + { + "DisplayName": "Georgia", + "Name": "GE", + "ThreeLetterISORegionName": "GEO", + "TwoLetterISORegionName": "GE" + }, + { + "DisplayName": "Germany", + "Name": "DE", + "ThreeLetterISORegionName": "DEU", + "TwoLetterISORegionName": "DE" + }, + { + "DisplayName": "Greece", + "Name": "GR", + "ThreeLetterISORegionName": "GRC", + "TwoLetterISORegionName": "GR" + }, + { + "DisplayName": "Greenland", + "Name": "GL", + "ThreeLetterISORegionName": "GRL", + "TwoLetterISORegionName": "GL" + }, + { + "DisplayName": "Guatemala", + "Name": "GT", + "ThreeLetterISORegionName": "GTM", + "TwoLetterISORegionName": "GT" + }, + { + "DisplayName": "Haiti", + "Name": "HT", + "ThreeLetterISORegionName": "HTI", + "TwoLetterISORegionName": "HT" + }, + { + "DisplayName": "Honduras", + "Name": "HN", + "ThreeLetterISORegionName": "HND", + "TwoLetterISORegionName": "HN" + }, + { + "DisplayName": "Hong Kong S.A.R.", + "Name": "HK", + "ThreeLetterISORegionName": "HKG", + "TwoLetterISORegionName": "HK" + }, + { + "DisplayName": "Hungary", + "Name": "HU", + "ThreeLetterISORegionName": "HUN", + "TwoLetterISORegionName": "HU" + }, + { + "DisplayName": "Iceland", + "Name": "IS", + "ThreeLetterISORegionName": "ISL", + "TwoLetterISORegionName": "IS" + }, + { + "DisplayName": "India", + "Name": "IN", + "ThreeLetterISORegionName": "IND", + "TwoLetterISORegionName": "IN" + }, + { + "DisplayName": "Indonesia", + "Name": "ID", + "ThreeLetterISORegionName": "IDN", + "TwoLetterISORegionName": "ID" + }, + { + "DisplayName": "Iran", + "Name": "IR", + "ThreeLetterISORegionName": "IRN", + "TwoLetterISORegionName": "IR" + }, + { + "DisplayName": "Iraq", + "Name": "IQ", + "ThreeLetterISORegionName": "IRQ", + "TwoLetterISORegionName": "IQ" + }, + { + "DisplayName": "Ireland", + "Name": "IE", + "ThreeLetterISORegionName": "IRL", + "TwoLetterISORegionName": "IE" + }, + { + "DisplayName": "Pakistan", + "Name": "PK", + "ThreeLetterISORegionName": "PAK", + "TwoLetterISORegionName": "PK" + }, + { + "DisplayName": "Israel", + "Name": "IL", + "ThreeLetterISORegionName": "ISR", + "TwoLetterISORegionName": "IL" + }, + { + "DisplayName": "Italy", + "Name": "IT", + "ThreeLetterISORegionName": "ITA", + "TwoLetterISORegionName": "IT" + }, + { + "DisplayName": "Ivory Coast", + "Name": "CI", + "ThreeLetterISORegionName": "CIV", + "TwoLetterISORegionName": "CI" + }, + { + "DisplayName": "Jamaica", + "Name": "JM", + "ThreeLetterISORegionName": "JAM", + "TwoLetterISORegionName": "JM" + }, + { + "DisplayName": "Japan", + "Name": "JP", + "ThreeLetterISORegionName": "JPN", + "TwoLetterISORegionName": "JP" + }, + { + "DisplayName": "Jordan", + "Name": "JO", + "ThreeLetterISORegionName": "JOR", + "TwoLetterISORegionName": "JO" + }, + { + "DisplayName": "Kazakhstan", + "Name": "KZ", + "ThreeLetterISORegionName": "KAZ", + "TwoLetterISORegionName": "KZ" + }, + { + "DisplayName": "Kenya", + "Name": "KE", + "ThreeLetterISORegionName": "KEN", + "TwoLetterISORegionName": "KE" + }, + { + "DisplayName": "Korea", + "Name": "KR", + "ThreeLetterISORegionName": "KOR", + "TwoLetterISORegionName": "KR" + }, + { + "DisplayName": "Kuwait", + "Name": "KW", + "ThreeLetterISORegionName": "KWT", + "TwoLetterISORegionName": "KW" + }, + { + "DisplayName": "Kyrgyzstan", + "Name": "KG", + "ThreeLetterISORegionName": "KGZ", + "TwoLetterISORegionName": "KG" + }, + { + "DisplayName": "Lao P.D.R.", + "Name": "LA", + "ThreeLetterISORegionName": "LAO", + "TwoLetterISORegionName": "LA" + }, + { + "DisplayName": "Latin America", + "Name": "419", + "ThreeLetterISORegionName": "419", + "TwoLetterISORegionName": "419" + }, + { + "DisplayName": "Latvia", + "Name": "LV", + "ThreeLetterISORegionName": "LVA", + "TwoLetterISORegionName": "LV" + }, + { + "DisplayName": "Lebanon", + "Name": "LB", + "ThreeLetterISORegionName": "LBN", + "TwoLetterISORegionName": "LB" + }, + { + "DisplayName": "Libya", + "Name": "LY", + "ThreeLetterISORegionName": "LBY", + "TwoLetterISORegionName": "LY" + }, + { + "DisplayName": "Liechtenstein", + "Name": "LI", + "ThreeLetterISORegionName": "LIE", + "TwoLetterISORegionName": "LI" + }, + { + "DisplayName": "Lithuania", + "Name": "LT", + "ThreeLetterISORegionName": "LTU", + "TwoLetterISORegionName": "LT" + }, + { + "DisplayName": "Luxembourg", + "Name": "LU", + "ThreeLetterISORegionName": "LUX", + "TwoLetterISORegionName": "LU" + }, + { + "DisplayName": "Macao S.A.R.", + "Name": "MO", + "ThreeLetterISORegionName": "MAC", + "TwoLetterISORegionName": "MO" + }, + { + "DisplayName": "Macedonia (FYROM)", + "Name": "MK", + "ThreeLetterISORegionName": "MKD", + "TwoLetterISORegionName": "MK" + }, + { + "DisplayName": "Malaysia", + "Name": "MY", + "ThreeLetterISORegionName": "MYS", + "TwoLetterISORegionName": "MY" + }, + { + "DisplayName": "Maldives", + "Name": "MV", + "ThreeLetterISORegionName": "MDV", + "TwoLetterISORegionName": "MV" + }, + { + "DisplayName": "Mali", + "Name": "ML", + "ThreeLetterISORegionName": "MLI", + "TwoLetterISORegionName": "ML" + }, + { + "DisplayName": "Malta", + "Name": "MT", + "ThreeLetterISORegionName": "MLT", + "TwoLetterISORegionName": "MT" + }, + { + "DisplayName": "Mexico", + "Name": "MX", + "ThreeLetterISORegionName": "MEX", + "TwoLetterISORegionName": "MX" + }, + { + "DisplayName": "Mongolia", + "Name": "MN", + "ThreeLetterISORegionName": "MNG", + "TwoLetterISORegionName": "MN" + }, + { + "DisplayName": "Montenegro", + "Name": "ME", + "ThreeLetterISORegionName": "MNE", + "TwoLetterISORegionName": "ME" + }, + { + "DisplayName": "Morocco", + "Name": "MA", + "ThreeLetterISORegionName": "MAR", + "TwoLetterISORegionName": "MA" + }, + { + "DisplayName": "Nepal", + "Name": "NP", + "ThreeLetterISORegionName": "NPL", + "TwoLetterISORegionName": "NP" + }, + { + "DisplayName": "Netherlands", + "Name": "NL", + "ThreeLetterISORegionName": "NLD", + "TwoLetterISORegionName": "NL" + }, + { + "DisplayName": "New Zealand", + "Name": "NZ", + "ThreeLetterISORegionName": "NZL", + "TwoLetterISORegionName": "NZ" + }, + { + "DisplayName": "Nicaragua", + "Name": "NI", + "ThreeLetterISORegionName": "NIC", + "TwoLetterISORegionName": "NI" + }, + { + "DisplayName": "Nigeria", + "Name": "NG", + "ThreeLetterISORegionName": "NGA", + "TwoLetterISORegionName": "NG" + }, + { + "DisplayName": "Norway", + "Name": "NO", + "ThreeLetterISORegionName": "NOR", + "TwoLetterISORegionName": "NO" + }, + { + "DisplayName": "Oman", + "Name": "OM", + "ThreeLetterISORegionName": "OMN", + "TwoLetterISORegionName": "OM" + }, + { + "DisplayName": "Palestine", + "Name": "PS", + "ThreeLetterISORegionName": "PSE", + "TwoLetterISORegionName": "PS" + }, + { + "DisplayName": "Panama", + "Name": "PA", + "ThreeLetterISORegionName": "PAN", + "TwoLetterISORegionName": "PA" + }, + { + "DisplayName": "Paraguay", + "Name": "PY", + "ThreeLetterISORegionName": "PRY", + "TwoLetterISORegionName": "PY" + }, + { + "DisplayName": "People's Republic of China", + "Name": "CN", + "ThreeLetterISORegionName": "CHN", + "TwoLetterISORegionName": "CN" + }, + { + "DisplayName": "Peru", + "Name": "PE", + "ThreeLetterISORegionName": "PER", + "TwoLetterISORegionName": "PE" + }, + { + "DisplayName": "Philippines", + "Name": "PH", + "ThreeLetterISORegionName": "PHL", + "TwoLetterISORegionName": "PH" + }, + { + "DisplayName": "Poland", + "Name": "PL", + "ThreeLetterISORegionName": "POL", + "TwoLetterISORegionName": "PL" + }, + { + "DisplayName": "Portugal", + "Name": "PT", + "ThreeLetterISORegionName": "PRT", + "TwoLetterISORegionName": "PT" + }, + { + "DisplayName": "Principality of Monaco", + "Name": "MC", + "ThreeLetterISORegionName": "MCO", + "TwoLetterISORegionName": "MC" + }, + { + "DisplayName": "Puerto Rico", + "Name": "PR", + "ThreeLetterISORegionName": "PRI", + "TwoLetterISORegionName": "PR" + }, + { + "DisplayName": "Qatar", + "Name": "QA", + "ThreeLetterISORegionName": "QAT", + "TwoLetterISORegionName": "QA" + }, + { + "DisplayName": "Republica Moldova", + "Name": "MD", + "ThreeLetterISORegionName": "MDA", + "TwoLetterISORegionName": "MD" + }, + { + "DisplayName": "Réunion", + "Name": "RE", + "ThreeLetterISORegionName": "REU", + "TwoLetterISORegionName": "RE" + }, + { + "DisplayName": "Romania", + "Name": "RO", + "ThreeLetterISORegionName": "ROU", + "TwoLetterISORegionName": "RO" + }, + { + "DisplayName": "Russia", + "Name": "RU", + "ThreeLetterISORegionName": "RUS", + "TwoLetterISORegionName": "RU" + }, + { + "DisplayName": "Rwanda", + "Name": "RW", + "ThreeLetterISORegionName": "RWA", + "TwoLetterISORegionName": "RW" + }, + { + "DisplayName": "Saudi Arabia", + "Name": "SA", + "ThreeLetterISORegionName": "SAU", + "TwoLetterISORegionName": "SA" + }, + { + "DisplayName": "Senegal", + "Name": "SN", + "ThreeLetterISORegionName": "SEN", + "TwoLetterISORegionName": "SN" + }, + { + "DisplayName": "Serbia", + "Name": "RS", + "ThreeLetterISORegionName": "SRB", + "TwoLetterISORegionName": "RS" + }, + { + "DisplayName": "Serbia and Montenegro (Former)", + "Name": "CS", + "ThreeLetterISORegionName": "SCG", + "TwoLetterISORegionName": "CS" + }, + { + "DisplayName": "Singapore", + "Name": "SG", + "ThreeLetterISORegionName": "SGP", + "TwoLetterISORegionName": "SG" + }, + { + "DisplayName": "Slovakia", + "Name": "SK", + "ThreeLetterISORegionName": "SVK", + "TwoLetterISORegionName": "SK" + }, + { + "DisplayName": "Slovenia", + "Name": "SI", + "ThreeLetterISORegionName": "SVN", + "TwoLetterISORegionName": "SI" + }, + { + "DisplayName": "Somalia", + "Name": "SO", + "ThreeLetterISORegionName": "SOM", + "TwoLetterISORegionName": "SO" + }, + { + "DisplayName": "South Africa", + "Name": "ZA", + "ThreeLetterISORegionName": "ZAF", + "TwoLetterISORegionName": "ZA" + }, + { + "DisplayName": "Spain", + "Name": "ES", + "ThreeLetterISORegionName": "ESP", + "TwoLetterISORegionName": "ES" + }, + { + "DisplayName": "Sri Lanka", + "Name": "LK", + "ThreeLetterISORegionName": "LKA", + "TwoLetterISORegionName": "LK" + }, + { + "DisplayName": "Sweden", + "Name": "SE", + "ThreeLetterISORegionName": "SWE", + "TwoLetterISORegionName": "SE" + }, + { + "DisplayName": "Switzerland", + "Name": "CH", + "ThreeLetterISORegionName": "CHE", + "TwoLetterISORegionName": "CH" + }, + { + "DisplayName": "Syria", + "Name": "SY", + "ThreeLetterISORegionName": "SYR", + "TwoLetterISORegionName": "SY" + }, + { + "DisplayName": "Taiwan", + "Name": "TW", + "ThreeLetterISORegionName": "TWN", + "TwoLetterISORegionName": "TW" + }, + { + "DisplayName": "Tajikistan", + "Name": "TJ", + "ThreeLetterISORegionName": "TAJ", + "TwoLetterISORegionName": "TJ" + }, + { + "DisplayName": "Thailand", + "Name": "TH", + "ThreeLetterISORegionName": "THA", + "TwoLetterISORegionName": "TH" + }, + { + "DisplayName": "Trinidad and Tobago", + "Name": "TT", + "ThreeLetterISORegionName": "TTO", + "TwoLetterISORegionName": "TT" + }, + { + "DisplayName": "Tunisia", + "Name": "TN", + "ThreeLetterISORegionName": "TUN", + "TwoLetterISORegionName": "TN" + }, + { + "DisplayName": "Turkey", + "Name": "TR", + "ThreeLetterISORegionName": "TUR", + "TwoLetterISORegionName": "TR" + }, + { + "DisplayName": "Turkmenistan", + "Name": "TM", + "ThreeLetterISORegionName": "TKM", + "TwoLetterISORegionName": "TM" + }, + { + "DisplayName": "U.A.E.", + "Name": "AE", + "ThreeLetterISORegionName": "ARE", + "TwoLetterISORegionName": "AE" + }, + { + "DisplayName": "Ukraine", + "Name": "UA", + "ThreeLetterISORegionName": "UKR", + "TwoLetterISORegionName": "UA" + }, + { + "DisplayName": "United Kingdom", + "Name": "GB", + "ThreeLetterISORegionName": "GBR", + "TwoLetterISORegionName": "GB" + }, + { + "DisplayName": "United States", + "Name": "US", + "ThreeLetterISORegionName": "USA", + "TwoLetterISORegionName": "US" + }, + { + "DisplayName": "Uruguay", + "Name": "UY", + "ThreeLetterISORegionName": "URY", + "TwoLetterISORegionName": "UY" + }, + { + "DisplayName": "Uzbekistan", + "Name": "UZ", + "ThreeLetterISORegionName": "UZB", + "TwoLetterISORegionName": "UZ" + }, + { + "DisplayName": "Vietnam", + "Name": "VN", + "ThreeLetterISORegionName": "VNM", + "TwoLetterISORegionName": "VN" + }, + { + "DisplayName": "Yemen", + "Name": "YE", + "ThreeLetterISORegionName": "YEM", + "TwoLetterISORegionName": "YE" + }, + { + "DisplayName": "Zimbabwe", + "Name": "ZW", + "ThreeLetterISORegionName": "ZWE", + "TwoLetterISORegionName": "ZW" + } +] diff --git a/Emby.Server.Implementations/Localization/iso6392.txt b/Emby.Server.Implementations/Localization/iso6392.txt new file mode 100644 index 00000000..d5a7e866 --- /dev/null +++ b/Emby.Server.Implementations/Localization/iso6392.txt @@ -0,0 +1,496 @@ +aar||aa|Afar|afar +abk||ab|Abkhazian|abkhaze +ace|||Achinese|aceh +ach|||Acoli|acoli +ada|||Adangme|adangme +ady|||Adyghe; Adygei|adyghé +afa|||Afro-Asiatic languages|afro-asiatiques, langues +afh|||Afrihili|afrihili +afr||af|Afrikaans|afrikaans +ain|||Ainu|aïnou +aka||ak|Akan|akan +akk|||Akkadian|akkadien +ale|||Aleut|aléoute +alg|||Algonquian languages|algonquines, langues +alt|||Southern Altai|altai du Sud +amh||am|Amharic|amharique +ang|||English, Old (ca.450-1100)|anglo-saxon (ca.450-1100) +anp|||Angika|angika +apa|||Apache languages|apaches, langues +ara||ar|Arabic|arabe +arc|||Official Aramaic (700-300 BCE); Imperial Aramaic (700-300 BCE)|araméen d'empire (700-300 BCE) +arg||an|Aragonese|aragonais +arn|||Mapudungun; Mapuche|mapudungun; mapuche; mapuce +arp|||Arapaho|arapaho +art|||Artificial languages|artificielles, langues +arw|||Arawak|arawak +asm||as|Assamese|assamais +ast|||Asturian; Bable; Leonese; Asturleonese|asturien; bable; léonais; asturoléonais +ath|||Athapascan languages|athapascanes, langues +aus|||Australian languages|australiennes, langues +ava||av|Avaric|avar +ave||ae|Avestan|avestique +awa|||Awadhi|awadhi +aym||ay|Aymara|aymara +aze||az|Azerbaijani|azéri +bad|||Banda languages|banda, langues +bai|||Bamileke languages|bamiléké, langues +bak||ba|Bashkir|bachkir +bal|||Baluchi|baloutchi +bam||bm|Bambara|bambara +ban|||Balinese|balinais +bas|||Basa|basa +bat|||Baltic languages|baltes, langues +bej|||Beja; Bedawiyet|bedja +bel||be|Belarusian|biélorusse +bem|||Bemba|bemba +ben||bn|Bengali|bengali +ber|||Berber languages|berbères, langues +bho|||Bhojpuri|bhojpuri +bih||bh|Bihari languages|langues biharis +bik|||Bikol|bikol +bin|||Bini; Edo|bini; edo +bis||bi|Bislama|bichlamar +bla|||Siksika|blackfoot +bnt|||Bantu (Other)|bantoues, autres langues +bod|tib|bo|Tibetan|tibétain +bos||bs|Bosnian|bosniaque +bra|||Braj|braj +bre||br|Breton|breton +btk|||Batak languages|batak, langues +bua|||Buriat|bouriate +bug|||Buginese|bugi +bul||bg|Bulgarian|bulgare +byn|||Blin; Bilin|blin; bilen +cad|||Caddo|caddo +cai|||Central American Indian languages|amérindiennes de L'Amérique centrale, langues +car|||Galibi Carib|karib; galibi; carib +cat||ca|Catalan; Valencian|catalan; valencien +cau|||Caucasian languages|caucasiennes, langues +ceb|||Cebuano|cebuano +cel|||Celtic languages|celtiques, langues; celtes, langues +ces|cze|cs|Czech|tchèque +cha||ch|Chamorro|chamorro +chb|||Chibcha|chibcha +che||ce|Chechen|tchétchène +chg|||Chagatai|djaghataï +chk|||Chuukese|chuuk +chm|||Mari|mari +chn|||Chinook jargon|chinook, jargon +cho|||Choctaw|choctaw +chp|||Chipewyan; Dene Suline|chipewyan +chr|||Cherokee|cherokee +chu||cu|Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic|slavon d'église; vieux slave; slavon liturgique; vieux bulgare +chv||cv|Chuvash|tchouvache +chy|||Cheyenne|cheyenne +cmc|||Chamic languages|chames, langues +cop|||Coptic|copte +cor||kw|Cornish|cornique +cos||co|Corsican|corse +cpe|||Creoles and pidgins, English based|créoles et pidgins basés sur l'anglais +cpf|||Creoles and pidgins, French-based |créoles et pidgins basés sur le français +cpp|||Creoles and pidgins, Portuguese-based |créoles et pidgins basés sur le portugais +cre||cr|Cree|cree +crh|||Crimean Tatar; Crimean Turkish|tatar de Crimé +crp|||Creoles and pidgins |créoles et pidgins +csb|||Kashubian|kachoube +cus|||Cushitic languages|couchitiques, langues +cym|wel|cy|Welsh|gallois +dak|||Dakota|dakota +dan||da|Danish|danois +dar|||Dargwa|dargwa +day|||Land Dayak languages|dayak, langues +del|||Delaware|delaware +den|||Slave (Athapascan)|esclave (athapascan) +deu|ger|de|German|allemand +dgr|||Dogrib|dogrib +din|||Dinka|dinka +div||dv|Divehi; Dhivehi; Maldivian|maldivien +doi|||Dogri|dogri +dra|||Dravidian languages|dravidiennes, langues +dsb|||Lower Sorbian|bas-sorabe +dua|||Duala|douala +dum|||Dutch, Middle (ca.1050-1350)|néerlandais moyen (ca. 1050-1350) +dyu|||Dyula|dioula +dzo||dz|Dzongkha|dzongkha +efi|||Efik|efik +egy|||Egyptian (Ancient)|égyptien +eka|||Ekajuk|ekajuk +ell|gre|el|Greek, Modern (1453-)|grec moderne (après 1453) +elx|||Elamite|élamite +eng||en|English|anglais +enm|||English, Middle (1100-1500)|anglais moyen (1100-1500) +epo||eo|Esperanto|espéranto +est||et|Estonian|estonien +eus|baq|eu|Basque|basque +ewe||ee|Ewe|éwé +ewo|||Ewondo|éwondo +fan|||Fang|fang +fao||fo|Faroese|féroïen +fas|per|fa|Persian|persan +fat|||Fanti|fanti +fij||fj|Fijian|fidjien +fil|||Filipino; Pilipino|filipino; pilipino +fin||fi|Finnish|finnois +fiu|||Finno-Ugrian languages|finno-ougriennes, langues +fon|||Fon|fon +fra|fre|fr|French|français +frm|||French, Middle (ca.1400-1600)|français moyen (1400-1600) +fro|||French, Old (842-ca.1400)|français ancien (842-ca.1400) +frc||fr-ca|French (Canada)|french +frr|||Northern Frisian|frison septentrional +frs|||Eastern Frisian|frison oriental +fry||fy|Western Frisian|frison occidental +ful||ff|Fulah|peul +fur|||Friulian|frioulan +gaa|||Ga|ga +gay|||Gayo|gayo +gba|||Gbaya|gbaya +gem|||Germanic languages|germaniques, langues +gez|||Geez|guèze +gil|||Gilbertese|kiribati +gla||gd|Gaelic; Scottish Gaelic|gaélique; gaélique écossais +gle||ga|Irish|irlandais +glg||gl|Galician|galicien +glv||gv|Manx|manx; mannois +gmh|||German, Middle High (ca.1050-1500)|allemand, moyen haut (ca. 1050-1500) +goh|||German, Old High (ca.750-1050)|allemand, vieux haut (ca. 750-1050) +gon|||Gondi|gond +gor|||Gorontalo|gorontalo +got|||Gothic|gothique +grb|||Grebo|grebo +grc|||Greek, Ancient (to 1453)|grec ancien (jusqu'à 1453) +grn||gn|Guarani|guarani +gsw|||Swiss German; Alemannic; Alsatian|suisse alémanique; alémanique; alsacien +guj||gu|Gujarati|goudjrati +gwi|||Gwich'in|gwich'in +hai|||Haida|haida +hat||ht|Haitian; Haitian Creole|haïtien; créole haïtien +hau||ha|Hausa|haoussa +haw|||Hawaiian|hawaïen +heb||he|Hebrew|hébreu +her||hz|Herero|herero +hil|||Hiligaynon|hiligaynon +him|||Himachali languages; Western Pahari languages|langues himachalis; langues paharis occidentales +hin||hi|Hindi|hindi +hit|||Hittite|hittite +hmn|||Hmong; Mong|hmong +hmo||ho|Hiri Motu|hiri motu +hrv||hr|Croatian|croate +hsb|||Upper Sorbian|haut-sorabe +hun||hu|Hungarian|hongrois +hup|||Hupa|hupa +hye|arm|hy|Armenian|arménien +iba|||Iban|iban +ibo||ig|Igbo|igbo +isl|ice|is|Icelandic|islandais +ido||io|Ido|ido +iii||ii|Sichuan Yi; Nuosu|yi de Sichuan +ijo|||Ijo languages|ijo, langues +iku||iu|Inuktitut|inuktitut +ile||ie|Interlingue; Occidental|interlingue +ilo|||Iloko|ilocano +ina||ia|Interlingua (International Auxiliary Language Association)|interlingua (langue auxiliaire internationale) +inc|||Indic languages|indo-aryennes, langues +ind||id|Indonesian|indonésien +ine|||Indo-European languages|indo-européennes, langues +inh|||Ingush|ingouche +ipk||ik|Inupiaq|inupiaq +ira|||Iranian languages|iraniennes, langues +iro|||Iroquoian languages|iroquoises, langues +ita||it|Italian|italien +jav||jv|Javanese|javanais +jbo|||Lojban|lojban +jpn||ja|Japanese|japonais +jpr|||Judeo-Persian|judéo-persan +jrb|||Judeo-Arabic|judéo-arabe +kaa|||Kara-Kalpak|karakalpak +kab|||Kabyle|kabyle +kac|||Kachin; Jingpho|kachin; jingpho +kal||kl|Kalaallisut; Greenlandic|groenlandais +kam|||Kamba|kamba +kan||kn|Kannada|kannada +kar|||Karen languages|karen, langues +kas||ks|Kashmiri|kashmiri +kat|geo|ka|Georgian|géorgien +kau||kr|Kanuri|kanouri +kaw|||Kawi|kawi +kaz||kk|Kazakh|kazakh +kbd|||Kabardian|kabardien +kha|||Khasi|khasi +khi|||Khoisan languages|khoïsan, langues +khm||km|Central Khmer|khmer central +kho|||Khotanese; Sakan|khotanais; sakan +kik||ki|Kikuyu; Gikuyu|kikuyu +kin||rw|Kinyarwanda|rwanda +kir||ky|Kirghiz; Kyrgyz|kirghiz +kmb|||Kimbundu|kimbundu +kok|||Konkani|konkani +kom||kv|Komi|kom +kon||kg|Kongo|kongo +kor||ko|Korean|coréen +kos|||Kosraean|kosrae +kpe|||Kpelle|kpellé +krc|||Karachay-Balkar|karatchai balkar +krl|||Karelian|carélien +kro|||Kru languages|krou, langues +kru|||Kurukh|kurukh +kua||kj|Kuanyama; Kwanyama|kuanyama; kwanyama +kum|||Kumyk|koumyk +kur||ku|Kurdish|kurde +kut|||Kutenai|kutenai +lad|||Ladino|judéo-espagnol +lah|||Lahnda|lahnda +lam|||Lamba|lamba +lao||lo|Lao|lao +lat||la|Latin|latin +lav||lv|Latvian|letton +lez|||Lezghian|lezghien +lim||li|Limburgan; Limburger; Limburgish|limbourgeois +lin||ln|Lingala|lingala +lit||lt|Lithuanian|lituanien +lol|||Mongo|mongo +loz|||Lozi|lozi +ltz||lb|Luxembourgish; Letzeburgesch|luxembourgeois +lua|||Luba-Lulua|luba-lulua +lub||lu|Luba-Katanga|luba-katanga +lug||lg|Ganda|ganda +lui|||Luiseno|luiseno +lun|||Lunda|lunda +luo|||Luo (Kenya and Tanzania)|luo (Kenya et Tanzanie) +lus|||Lushai|lushai +mad|||Madurese|madourais +mag|||Magahi|magahi +mah||mh|Marshallese|marshall +mai|||Maithili|maithili +mak|||Makasar|makassar +mal||ml|Malayalam|malayalam +man|||Mandingo|mandingue +map|||Austronesian languages|austronésiennes, langues +mar||mr|Marathi|marathe +mas|||Masai|massaï +mdf|||Moksha|moksa +mdr|||Mandar|mandar +men|||Mende|mendé +mga|||Irish, Middle (900-1200)|irlandais moyen (900-1200) +mic|||Mi'kmaq; Micmac|mi'kmaq; micmac +min|||Minangkabau|minangkabau +mis|||Uncoded languages|langues non codées +mkd|mac|mk|Macedonian|macédonien +mkh|||Mon-Khmer languages|môn-khmer, langues +mlg||mg|Malagasy|malgache +mlt||mt|Maltese|maltais +mnc|||Manchu|mandchou +mni|||Manipuri|manipuri +mno|||Manobo languages|manobo, langues +moh|||Mohawk|mohawk +mon||mn|Mongolian|mongol +mos|||Mossi|moré +mri|mao|mi|Maori|maori +msa|may|ms|Malay|malais +mul|||Multiple languages|multilingue +mun|||Munda languages|mounda, langues +mus|||Creek|muskogee +mwl|||Mirandese|mirandais +mwr|||Marwari|marvari +mya|bur|my|Burmese|birman +myn|||Mayan languages|maya, langues +myv|||Erzya|erza +nah|||Nahuatl languages|nahuatl, langues +nai|||North American Indian languages|nord-amérindiennes, langues +nap|||Neapolitan|napolitain +nau||na|Nauru|nauruan +nav||nv|Navajo; Navaho|navaho +nbl||nr|Ndebele, South; South Ndebele|ndébélé du Sud +nde||nd|Ndebele, North; North Ndebele|ndébélé du Nord +ndo||ng|Ndonga|ndonga +nds|||Low German; Low Saxon; German, Low; Saxon, Low|bas allemand; bas saxon; allemand, bas; saxon, bas +nep||ne|Nepali|népalais +new|||Nepal Bhasa; Newari|nepal bhasa; newari +nia|||Nias|nias +nic|||Niger-Kordofanian languages|nigéro-kordofaniennes, langues +niu|||Niuean|niué +nld|dut|nl|Dutch; Flemish|néerlandais; flamand +nno||nn|Norwegian (Nynorsk)|norvégien (nynorsk) +nob||nb|Norwegian (Bokmal)|norvégien (bokmål) +nog|||Nogai|nogaï; nogay +non|||Norse, Old|norrois, vieux +nor||no|Norwegian|norvégien +nqo|||N'Ko|n'ko +nso|||Pedi; Sepedi; Northern Sotho|pedi; sepedi; sotho du Nord +nub|||Nubian languages|nubiennes, langues +nwc|||Classical Newari; Old Newari; Classical Nepal Bhasa|newari classique +nya||ny|Chichewa; Chewa; Nyanja|chichewa; chewa; nyanja +nym|||Nyamwezi|nyamwezi +nyn|||Nyankole|nyankolé +nyo|||Nyoro|nyoro +nzi|||Nzima|nzema +oci||oc|Occitan (post 1500); Provençal|occitan (après 1500); provençal +oji||oj|Ojibwa|ojibwa +ori||or|Oriya|oriya +orm||om|Oromo|galla +osa|||Osage|osage +oss||os|Ossetian; Ossetic|ossète +ota|||Turkish, Ottoman (1500-1928)|turc ottoman (1500-1928) +oto|||Otomian languages|otomi, langues +paa|||Papuan languages|papoues, langues +pag|||Pangasinan|pangasinan +pal|||Pahlavi|pahlavi +pam|||Pampanga; Kapampangan|pampangan +pan||pa|Panjabi; Punjabi|pendjabi +pap|||Papiamento|papiamento +pau|||Palauan|palau +peo|||Persian, Old (ca.600-400 B.C.)|perse, vieux (ca. 600-400 av. J.-C.) +phi|||Philippine languages|philippines, langues +phn|||Phoenician|phénicien +pli||pi|Pali|pali +pol||pl|Polish|polonais +pon|||Pohnpeian|pohnpei +por||pt|Portuguese|portugais +por||pt-pt|Portuguese (Portugal)|portugais (pt-pt) +por||pt-br|Portuguese (Brazil)|portugais (pt-br) +pra|||Prakrit languages|prâkrit, langues +pro|||Provençal, Old (to 1500)|provençal ancien (jusqu'à 1500) +pus||ps|Pushto; Pashto|pachto +qaa-qtz|||Reserved for local use|réservée à l'usage local +que||qu|Quechua|quechua +raj|||Rajasthani|rajasthani +rap|||Rapanui|rapanui +rar|||Rarotongan; Cook Islands Maori|rarotonga; maori des îles Cook +roa|||Romance languages|romanes, langues +roh||rm|Romansh|romanche +rom|||Romany|tsigane +ron|rum|ro|Romanian; Moldavian; Moldovan|roumain; moldave +run||rn|Rundi|rundi +rup|||Aromanian; Arumanian; Macedo-Romanian|aroumain; macédo-roumain +rus||ru|Russian|russe +sad|||Sandawe|sandawe +sag||sg|Sango|sango +sah|||Yakut|iakoute +sai|||South American Indian (Other)|indiennes d'Amérique du Sud, autres langues +sal|||Salishan languages|salishennes, langues +sam|||Samaritan Aramaic|samaritain +san||sa|Sanskrit|sanskrit +sas|||Sasak|sasak +sat|||Santali|santal +srp||sr|Serbian|serbe +scn|||Sicilian|sicilien +sco|||Scots|écossais +sel|||Selkup|selkoupe +sem|||Semitic languages|sémitiques, langues +sga|||Irish, Old (to 900)|irlandais ancien (jusqu'à 900) +sgn|||Sign Languages|langues des signes +shn|||Shan|chan +sid|||Sidamo|sidamo +sin||si|Sinhala; Sinhalese|singhalais +sio|||Siouan languages|sioux, langues +sit|||Sino-Tibetan languages|sino-tibétaines, langues +sla|||Slavic languages|slaves, langues +slk|slo|sk|Slovak|slovaque +slv||sl|Slovenian|slovène +sma|||Southern Sami|sami du Sud +sme||se|Northern Sami|sami du Nord +smi|||Sami languages|sames, langues +smj|||Sami (Lule)|sami de Lule +smn|||Sami (Inari)|sami d'Inari +smo||sm|Samoan|samoan +sms|||Sami (Skolt)|sami skolt +sna||sn|Shona|shona +snd||sd|Sindhi|sindhi +snk|||Soninke|soninké +sog|||Sogdian|sogdien +som||so|Somali|somali +son|||Songhai languages|songhai, langues +sot||st|Sotho, Southern|sotho du Sud +spa||es|Spanish; Castilian|espagnol; castillan +spa||es-419|Spanish; Latin|espagnol; Latin +sqi|alb|sq|Albanian|albanais +srd||sc|Sardinian|sarde +srn|||Sranan Tongo|sranan tongo +srr|||Serer|sérère +ssa|||Nilo-Saharan languages|nilo-sahariennes, langues +ssw||ss|Swati|swati +suk|||Sukuma|sukuma +sun||su|Sundanese|soundanais +sus|||Susu|soussou +sux|||Sumerian|sumérien +swa||sw|Swahili|swahili +swe||sv|Swedish|suédois +syc|||Classical Syriac|syriaque classique +syr|||Syriac|syriaque +tah||ty|Tahitian|tahitien +tai|||Tai languages|tai, langues +tam||ta|Tamil|tamoul +tat||tt|Tatar|tatar +tel||te|Telugu|télougou +tem|||Timne|temne +ter|||Tereno|tereno +tet|||Tetum|tetum +tgk||tg|Tajik|tadjik +tgl||tl|Tagalog|tagalog +tha||th|Thai|thaï +tig|||Tigre|tigré +tir||ti|Tigrinya|tigrigna +tiv|||Tiv|tiv +tkl|||Tokelau|tokelau +tlh|||Klingon; tlhIngan-Hol|klingon +tli|||Tlingit|tlingit +tmh|||Tamashek|tamacheq +tog|||Tonga (Nyasa)|tonga (Nyasa) +ton||to|Tonga (Tonga Islands)|tongan (Îles Tonga) +tpi|||Tok Pisin|tok pisin +tsi|||Tsimshian|tsimshian +tsn||tn|Tswana|tswana +tso||ts|Tsonga|tsonga +tuk||tk|Turkmen|turkmène +tum|||Tumbuka|tumbuka +tup|||Tupi languages|tupi, langues +tur||tr|Turkish|turc +tut|||Altaic languages|altaïques, langues +tvl|||Tuvalu|tuvalu +twi||tw|Twi|twi +tyv|||Tuvinian|touva +udm|||Udmurt|oudmourte +uga|||Ugaritic|ougaritique +uig||ug|Uighur; Uyghur|ouïgour +ukr||uk|Ukrainian|ukrainien +umb|||Umbundu|umbundu +und|||Undetermined|indéterminée +urd||ur|Urdu|ourdou +uzb||uz|Uzbek|ouszbek +vai|||Vai|vaï +ven||ve|Venda|venda +vie||vi|Vietnamese|vietnamien +vol||vo|Volapük|volapük +vot|||Votic|vote +wak|||Wakashan languages|wakashanes, langues +wal|||Walamo|walamo +war|||Waray|waray +was|||Washo|washo +wen|||Sorbian languages|sorabes, langues +wln||wa|Walloon|wallon +wol||wo|Wolof|wolof +xal|||Kalmyk; Oirat|kalmouk; oïrat +xho||xh|Xhosa|xhosa +yao|||Yao|yao +yap|||Yapese|yapois +yid||yi|Yiddish|yiddish +yor||yo|Yoruba|yoruba +ypk|||Yupik languages|yupik, langues +zap|||Zapotec|zapotèque +zbl|||Blissymbols; Blissymbolics; Bliss|symboles Bliss; Bliss +zen|||Zenaga|zenaga +zgh|||Standard Moroccan Tamazight|amazighe standard marocain +zha||za|Zhuang; Chuang|zhuang; chuang +zho|chi|zh|Chinese|chinois +zho|chi|ze|Chinese (Bilingual)|chinois +zho|chi|zh-cn|Chinese (Simplified)|chinois +zho|chi|zh-hans|Chinese (Simplified)|chinois +zho|chi|zh-tw|Chinese (Traditional)|chinois +zho|chi|zh-hant|Chinese (Traditional)|chinois +zho|chi|zh-hk|Chinese (Hong Kong)|chinois +znd|||Zande languages|zandé, langues +zul||zu|Zulu|zoulou +zun|||Zuni|zuni +zxx|||No linguistic content; Not applicable|pas de contenu linguistique; non applicable +zza|||Zaza; Dimili; Dimli; Kirdki; Kirmanjki; Zazaki|zaza; dimili; dimli; kirdki; kirmanjki; zazaki diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs new file mode 100644 index 00000000..40941413 --- /dev/null +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -0,0 +1,688 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Extensions; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Playlists; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using PlaylistsNET.Content; +using PlaylistsNET.Models; +using Genre = MediaBrowser.Controller.Entities.Genre; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; + +namespace Emby.Server.Implementations.Playlists +{ + public class PlaylistManager : IPlaylistManager + { + private readonly ILibraryManager _libraryManager; + private readonly IFileSystem _fileSystem; + private readonly ILibraryMonitor _iLibraryMonitor; + private readonly ILogger<PlaylistManager> _logger; + private readonly IUserManager _userManager; + private readonly IProviderManager _providerManager; + private readonly IConfiguration _appConfig; + + public PlaylistManager( + ILibraryManager libraryManager, + IFileSystem fileSystem, + ILibraryMonitor iLibraryMonitor, + ILogger<PlaylistManager> logger, + IUserManager userManager, + IProviderManager providerManager, + IConfiguration appConfig) + { + _libraryManager = libraryManager; + _fileSystem = fileSystem; + _iLibraryMonitor = iLibraryMonitor; + _logger = logger; + _userManager = userManager; + _providerManager = providerManager; + _appConfig = appConfig; + } + + public Playlist GetPlaylistForUser(Guid playlistId, Guid userId) + { + return GetPlaylists(userId).Where(p => p.Id.Equals(playlistId)).FirstOrDefault(); + } + + public IEnumerable<Playlist> GetPlaylists(Guid userId) + { + var user = _userManager.GetUserById(userId); + return _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = [BaseItemKind.Playlist], + Recursive = true, + DtoOptions = new DtoOptions(false) + }) + .Cast<Playlist>() + .Where(p => p.IsVisible(user)); + } + + public async Task<PlaylistCreationResult> CreatePlaylist(PlaylistCreationRequest request) + { + var name = request.Name; + var folderName = _fileSystem.GetValidFilename(name); + var parentFolder = GetPlaylistsFolder(request.UserId); + if (parentFolder is null) + { + throw new ArgumentException(nameof(parentFolder)); + } + + if (request.MediaType is null || request.MediaType == MediaType.Unknown) + { + foreach (var itemId in request.ItemIdList) + { + var item = _libraryManager.GetItemById(itemId) ?? throw new ArgumentException("No item exists with the supplied Id"); + if (item.MediaType != MediaType.Unknown) + { + request.MediaType = item.MediaType; + } + else if (item is MusicArtist || item is MusicAlbum || item is MusicGenre) + { + request.MediaType = MediaType.Audio; + } + else if (item is Genre) + { + request.MediaType = MediaType.Video; + } + else + { + if (item is Folder folder) + { + request.MediaType = folder.GetRecursiveChildren(i => !i.IsFolder && i.SupportsAddingToPlaylist) + .Select(i => i.MediaType) + .FirstOrDefault(i => i != MediaType.Unknown); + } + } + + if (request.MediaType is not null && request.MediaType != MediaType.Unknown) + { + break; + } + } + } + + if (request.MediaType is null || request.MediaType == MediaType.Unknown) + { + request.MediaType = MediaType.Audio; + } + + var user = _userManager.GetUserById(request.UserId); + var path = Path.Combine(parentFolder.Path, folderName); + path = GetTargetPath(path); + + _iLibraryMonitor.ReportFileSystemChangeBeginning(path); + + try + { + var info = Directory.CreateDirectory(path); + var playlist = new Playlist + { + Name = name, + Path = path, + OwnerUserId = request.UserId, + Shares = request.Users ?? [], + OpenAccess = request.Public ?? false, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc + }; + + playlist.SetMediaType(request.MediaType); + parentFolder.AddChild(playlist); + + await playlist.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ForceSave = true }, CancellationToken.None) + .ConfigureAwait(false); + + if (request.ItemIdList.Count > 0) + { + await AddToPlaylistInternal(playlist.Id, request.ItemIdList, user, new DtoOptions(false) + { + EnableImages = true + }).ConfigureAwait(false); + } + + return new PlaylistCreationResult(playlist.Id.ToString("N", CultureInfo.InvariantCulture)); + } + finally + { + // Refresh handled internally + _iLibraryMonitor.ReportFileSystemChangeComplete(path, false); + } + } + + private List<Playlist> GetUserPlaylists(Guid userId) + { + var user = _userManager.GetUserById(userId); + var playlistsFolder = GetPlaylistsFolder(userId); + if (playlistsFolder is null) + { + return []; + } + + return playlistsFolder.GetChildren(user, true).OfType<Playlist>().ToList(); + } + + private static string GetTargetPath(string path) + { + while (Directory.Exists(path)) + { + path += "1"; + } + + return path; + } + + private IReadOnlyList<BaseItem> GetPlaylistItems(IEnumerable<Guid> itemIds, User user, DtoOptions options) + { + var items = itemIds.Select(_libraryManager.GetItemById).Where(i => i is not null); + + return Playlist.GetPlaylistItems(items, user, options); + } + + public Task AddItemToPlaylistAsync(Guid playlistId, IReadOnlyCollection<Guid> itemIds, int? position, Guid userId) + { + var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId); + + return AddToPlaylistInternal( + playlistId, + itemIds, + user, + new DtoOptions(false) + { + EnableImages = true + }, + position); + } + + private async Task AddToPlaylistInternal(Guid playlistId, IReadOnlyCollection<Guid> newItemIds, User user, DtoOptions options, int? position = null) + { + // Retrieve the existing playlist + var playlist = _libraryManager.GetItemById(playlistId) as Playlist + ?? throw new ArgumentException("No Playlist exists with Id " + playlistId); + + // Retrieve all the items to be added to the playlist + var newItems = GetPlaylistItems(newItemIds, user, options) + .Where(i => i.SupportsAddingToPlaylist); + + // Filter out duplicate items + var existingIds = playlist.LinkedChildren.Select(c => c.ItemId).ToHashSet(); + newItems = newItems + .Where(i => !existingIds.Contains(i.Id)) + .Distinct(); + + // Create a list of the new linked children to add to the playlist + var childrenToAdd = newItems + .Select(LinkedChild.Create) + .ToList(); + + // Log duplicates that have been ignored, if any + int numDuplicates = newItemIds.Count - childrenToAdd.Count; + if (numDuplicates > 0) + { + _logger.LogWarning("Ignored adding {DuplicateCount} duplicate items to playlist {PlaylistName}.", numDuplicates, playlist.Name); + } + + // Do nothing else if there are no items to add to the playlist + if (childrenToAdd.Count == 0) + { + return; + } + + // Update the playlist in the repository + if (position.HasValue) + { + if (position.Value <= 0) + { + playlist.LinkedChildren = [.. childrenToAdd, .. playlist.LinkedChildren]; + } + else if (position.Value >= playlist.LinkedChildren.Length) + { + playlist.LinkedChildren = [.. playlist.LinkedChildren, .. childrenToAdd]; + } + else + { + playlist.LinkedChildren = [ + .. playlist.LinkedChildren[0..position.Value], + .. childrenToAdd, + .. playlist.LinkedChildren[position.Value..playlist.LinkedChildren.Length] + ]; + } + } + else + { + playlist.LinkedChildren = [.. playlist.LinkedChildren, .. childrenToAdd]; + } + + playlist.DateLastMediaAdded = DateTime.UtcNow; + + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); + + // Refresh playlist metadata + _providerManager.QueueRefresh( + playlist.Id, + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + ForceSave = true + }, + RefreshPriority.High); + } + + public async Task RemoveItemFromPlaylistAsync(string playlistId, IEnumerable<string> entryIds) + { + if (_libraryManager.GetItemById(playlistId) is not Playlist playlist) + { + throw new ArgumentException("No Playlist exists with the supplied Id"); + } + + var children = playlist.GetManageableItems().ToList(); + + var idList = entryIds.ToList(); + + var removals = children.Where(i => idList.Contains(i.Item1.ItemId?.ToString("N", CultureInfo.InvariantCulture))); + + playlist.LinkedChildren = children.Except(removals) + .Select(i => i.Item1) + .ToArray(); + + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); + + _providerManager.QueueRefresh( + playlist.Id, + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + ForceSave = true + }, + RefreshPriority.High); + } + + internal static int DetermineAdjustedIndex(int newPriorIndexAllChildren, int newIndex) + { + if (newIndex == 0) + { + return newPriorIndexAllChildren > 0 ? newPriorIndexAllChildren - 1 : 0; + } + + return newPriorIndexAllChildren + 1; + } + + public async Task MoveItemAsync(string playlistId, string entryId, int newIndex, Guid callingUserId) + { + if (_libraryManager.GetItemById(playlistId) is not Playlist playlist) + { + throw new ArgumentException("No Playlist exists with the supplied Id"); + } + + var user = _userManager.GetUserById(callingUserId); + var children = playlist.GetManageableItems().ToList(); + var accessibleChildren = children.Where(c => c.Item2.IsVisible(user)).ToArray(); + + var oldIndexAll = children.FindIndex(i => string.Equals(entryId, i.Item1.ItemId?.ToString("N", CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase)); + var oldIndexAccessible = accessibleChildren.FindIndex(i => string.Equals(entryId, i.Item1.ItemId?.ToString("N", CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase)); + + if (oldIndexAccessible == newIndex) + { + return; + } + + var newPriorItemIndex = Math.Max(newIndex - 1, 0); + var newPriorItemId = accessibleChildren[newPriorItemIndex].Item1.ItemId; + var newPriorItemIndexOnAllChildren = children.FindIndex(c => c.Item1.ItemId.Equals(newPriorItemId)); + var adjustedNewIndex = DetermineAdjustedIndex(newPriorItemIndexOnAllChildren, newIndex); + + var item = playlist.LinkedChildren.FirstOrDefault(i => string.Equals(entryId, i.ItemId?.ToString("N", CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase)); + if (item is null) + { + _logger.LogWarning("Modified item not found in playlist. ItemId: {ItemId}, PlaylistId: {PlaylistId}", entryId, playlistId); + + return; + } + + var newList = playlist.LinkedChildren.ToList(); + newList.Remove(item); + + if (newIndex >= newList.Count) + { + newList.Add(item); + } + else + { + newList.Insert(adjustedNewIndex, item); + } + + playlist.LinkedChildren = [.. newList]; + + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); + } + + /// <inheritdoc /> + public void SavePlaylistFile(Playlist item) + { + // this is probably best done as a metadata provider + // saving a file over itself will require some work to prevent this from happening when not needed + var playlistPath = item.Path; + var extension = Path.GetExtension(playlistPath.AsSpan()); + + if (extension.Equals(".wpl", StringComparison.OrdinalIgnoreCase)) + { + var playlist = new WplPlaylist(); + foreach (var child in item.GetLinkedChildren()) + { + var entry = new WplPlaylistEntry() + { + Path = NormalizeItemPath(playlistPath, child.Path), + TrackTitle = child.Name, + AlbumTitle = child.Album + }; + + if (child is IHasAlbumArtist hasAlbumArtist) + { + entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null; + } + + if (child is IHasArtist hasArtist) + { + entry.TrackArtist = hasArtist.Artists.Count > 0 ? hasArtist.Artists[0] : null; + } + + if (child.RunTimeTicks.HasValue) + { + entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value); + } + + playlist.PlaylistEntries.Add(entry); + } + + string text = new WplContent().ToText(playlist); + File.WriteAllText(playlistPath, text); + } + else if (extension.Equals(".zpl", StringComparison.OrdinalIgnoreCase)) + { + var playlist = new ZplPlaylist(); + foreach (var child in item.GetLinkedChildren()) + { + var entry = new ZplPlaylistEntry() + { + Path = NormalizeItemPath(playlistPath, child.Path), + TrackTitle = child.Name, + AlbumTitle = child.Album + }; + + if (child is IHasAlbumArtist hasAlbumArtist) + { + entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null; + } + + if (child is IHasArtist hasArtist) + { + entry.TrackArtist = hasArtist.Artists.Count > 0 ? hasArtist.Artists[0] : null; + } + + if (child.RunTimeTicks.HasValue) + { + entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value); + } + + playlist.PlaylistEntries.Add(entry); + } + + string text = new ZplContent().ToText(playlist); + File.WriteAllText(playlistPath, text); + } + else if (extension.Equals(".m3u", StringComparison.OrdinalIgnoreCase)) + { + var playlist = new M3uPlaylist + { + IsExtended = true + }; + foreach (var child in item.GetLinkedChildren()) + { + var entry = new M3uPlaylistEntry() + { + Path = NormalizeItemPath(playlistPath, child.Path), + Title = child.Name, + Album = child.Album + }; + + if (child is IHasAlbumArtist hasAlbumArtist) + { + entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null; + } + + if (child.RunTimeTicks.HasValue) + { + entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value); + } + + playlist.PlaylistEntries.Add(entry); + } + + string text = new M3uContent().ToText(playlist); + File.WriteAllText(playlistPath, text); + } + else if (extension.Equals(".m3u8", StringComparison.OrdinalIgnoreCase)) + { + var playlist = new M3uPlaylist + { + IsExtended = true + }; + + foreach (var child in item.GetLinkedChildren()) + { + var entry = new M3uPlaylistEntry() + { + Path = NormalizeItemPath(playlistPath, child.Path), + Title = child.Name, + Album = child.Album + }; + + if (child is IHasAlbumArtist hasAlbumArtist) + { + entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null; + } + + if (child.RunTimeTicks.HasValue) + { + entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value); + } + + playlist.PlaylistEntries.Add(entry); + } + + string text = new M3uContent().ToText(playlist); + File.WriteAllText(playlistPath, text); + } + else if (extension.Equals(".pls", StringComparison.OrdinalIgnoreCase)) + { + var playlist = new PlsPlaylist(); + foreach (var child in item.GetLinkedChildren()) + { + var entry = new PlsPlaylistEntry() + { + Path = NormalizeItemPath(playlistPath, child.Path), + Title = child.Name + }; + + if (child.RunTimeTicks.HasValue) + { + entry.Length = TimeSpan.FromTicks(child.RunTimeTicks.Value); + } + + playlist.PlaylistEntries.Add(entry); + } + + string text = new PlsContent().ToText(playlist); + File.WriteAllText(playlistPath, text); + } + } + + private static string NormalizeItemPath(string playlistPath, string itemPath) + { + return MakeRelativePath(Path.GetDirectoryName(playlistPath), itemPath); + } + + private static string MakeRelativePath(string folderPath, string fileAbsolutePath) + { + ArgumentException.ThrowIfNullOrEmpty(folderPath); + ArgumentException.ThrowIfNullOrEmpty(fileAbsolutePath); + + if (!folderPath.EndsWith(Path.DirectorySeparatorChar)) + { + folderPath += Path.DirectorySeparatorChar; + } + + var folderUri = new Uri(folderPath); + var fileAbsoluteUri = new Uri(fileAbsolutePath); + + // path can't be made relative + if (folderUri.Scheme != fileAbsoluteUri.Scheme) + { + return fileAbsolutePath; + } + + var relativeUri = folderUri.MakeRelativeUri(fileAbsoluteUri); + string relativePath = Uri.UnescapeDataString(relativeUri.ToString()); + + if (fileAbsoluteUri.Scheme.Equals("file", StringComparison.OrdinalIgnoreCase)) + { + relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + } + + return relativePath; + } + + /// <inheritdoc /> + public Folder GetPlaylistsFolder() + { + return GetPlaylistsFolder(Guid.Empty); + } + + /// <inheritdoc /> + public Folder GetPlaylistsFolder(Guid userId) + { + const string TypeName = "PlaylistsFolder"; + + return _libraryManager.RootFolder.Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, TypeName, StringComparison.Ordinal)) ?? + _libraryManager.GetUserRootFolder().Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, TypeName, StringComparison.Ordinal)); + } + + /// <inheritdoc /> + public async Task RemovePlaylistsAsync(Guid userId) + { + var playlists = GetUserPlaylists(userId); + foreach (var playlist in playlists) + { + // Update owner if shared + var rankedShares = playlist.Shares.OrderByDescending(x => x.CanEdit).ToList(); + if (rankedShares.Count > 0) + { + playlist.OwnerUserId = rankedShares[0].UserId; + playlist.Shares = rankedShares.Skip(1).ToArray(); + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); + } + else if (!playlist.OpenAccess) + { + // Remove playlist if not shared + _libraryManager.DeleteItem( + playlist, + new DeleteOptions + { + DeleteFileLocation = false, + DeleteFromExternalProvider = false + }, + playlist.GetParent(), + false); + } + } + } + + public async Task UpdatePlaylist(PlaylistUpdateRequest request) + { + var playlist = GetPlaylistForUser(request.Id, request.UserId); + + if (request.Ids is not null) + { + playlist.LinkedChildren = []; + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); + + var user = _userManager.GetUserById(request.UserId); + await AddToPlaylistInternal(request.Id, request.Ids, user, new DtoOptions(false) + { + EnableImages = true + }).ConfigureAwait(false); + + playlist = GetPlaylistForUser(request.Id, request.UserId); + } + + if (request.Name is not null) + { + playlist.Name = request.Name; + } + + if (request.Users is not null) + { + playlist.Shares = request.Users; + } + + if (request.Public is not null) + { + playlist.OpenAccess = request.Public.Value; + } + + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); + } + + public async Task AddUserToShares(PlaylistUserUpdateRequest request) + { + var userId = request.UserId; + var playlist = GetPlaylistForUser(request.Id, userId); + var shares = playlist.Shares.ToList(); + var existingUserShare = shares.FirstOrDefault(s => s.UserId.Equals(userId)); + if (existingUserShare is not null) + { + shares.Remove(existingUserShare); + } + + shares.Add(new PlaylistUserPermissions(userId, request.CanEdit ?? false)); + playlist.Shares = shares; + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); + } + + public async Task RemoveUserFromShares(Guid playlistId, Guid userId, PlaylistUserPermissions share) + { + var playlist = GetPlaylistForUser(playlistId, userId); + var shares = playlist.Shares.ToList(); + shares.Remove(share); + playlist.Shares = shares; + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); + } + + private async Task UpdatePlaylistInternal(Playlist playlist) + { + await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + + if (playlist.IsFile) + { + SavePlaylistFile(playlist); + } + } + } +} diff --git a/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs new file mode 100644 index 00000000..a5be2b61 --- /dev/null +++ b/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs @@ -0,0 +1,55 @@ +#pragma warning disable CS1591 + +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Common; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Playlists; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Playlists +{ + [RequiresSourceSerialisation] + public class PlaylistsFolder : BasePluginFolder + { + public PlaylistsFolder() + { + Name = "Playlists"; + } + + [JsonIgnore] + public override bool IsHidden => true; + + [JsonIgnore] + public override bool SupportsInheritedParentImages => false; + + [JsonIgnore] + public override CollectionType? CollectionType => Jellyfin.Data.Enums.CollectionType.playlists; + + protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user) + { + return base.GetEligibleChildrenForRecursiveChildren(user).OfType<Playlist>(); + } + + protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query) + { + if (query.User is null) + { + query.Recursive = false; + return base.GetItemsInternal(query); + } + + query.Recursive = true; + query.IncludeItemTypes = new[] { BaseItemKind.Playlist }; + return QueryWithPostFiltering2(query); + } + + public override string GetClientTypeName() + { + return "ManualPlaylistsFolder"; + } + } +} diff --git a/Emby.Server.Implementations/Plugins/PluginLoadContext.cs b/Emby.Server.Implementations/Plugins/PluginLoadContext.cs new file mode 100644 index 00000000..d04e9cf6 --- /dev/null +++ b/Emby.Server.Implementations/Plugins/PluginLoadContext.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.Loader; + +namespace Emby.Server.Implementations.Plugins; + +/// <summary> +/// A custom <see cref="AssemblyLoadContext"/> for loading Jellyfin plugins. +/// </summary> +public class PluginLoadContext : AssemblyLoadContext +{ + private readonly AssemblyDependencyResolver _resolver; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginLoadContext"/> class. + /// </summary> + /// <param name="path">The path of the plugin assembly.</param> + public PluginLoadContext(string path) : base(true) + { + _resolver = new AssemblyDependencyResolver(path); + } + + /// <inheritdoc /> + protected override Assembly? Load(AssemblyName assemblyName) + { + var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); + if (assemblyPath is not null) + { + return LoadFromAssemblyPath(assemblyPath); + } + + return null; + } +} diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs new file mode 100644 index 00000000..91ccb16e --- /dev/null +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -0,0 +1,913 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Reflection; +using System.Runtime.Loader; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Emby.Server.Implementations.Library; +using Jellyfin.Extensions.Json; +using Jellyfin.Extensions.Json.Converters; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Updates; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Plugins +{ + /// <summary> + /// Defines the <see cref="PluginManager" />. + /// </summary> + public sealed class PluginManager : IPluginManager, IDisposable + { + private const string MetafileName = "meta.json"; + + private readonly string _pluginsPath; + private readonly Version _appVersion; + private readonly List<AssemblyLoadContext> _assemblyLoadContexts; + private readonly JsonSerializerOptions _jsonOptions; + private readonly ILogger<PluginManager> _logger; + private readonly IServerApplicationHost _appHost; + private readonly ServerConfiguration _config; + private readonly List<LocalPlugin> _plugins; + private readonly Version _minimumVersion; + + private IHttpClientFactory? _httpClientFactory; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginManager"/> class. + /// </summary> + /// <param name="logger">The <see cref="ILogger{PluginManager}"/>.</param> + /// <param name="appHost">The <see cref="IServerApplicationHost"/>.</param> + /// <param name="config">The <see cref="ServerConfiguration"/>.</param> + /// <param name="pluginsPath">The plugin path.</param> + /// <param name="appVersion">The application version.</param> + public PluginManager( + ILogger<PluginManager> logger, + IServerApplicationHost appHost, + ServerConfiguration config, + string pluginsPath, + Version appVersion) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _pluginsPath = pluginsPath; + _appVersion = appVersion ?? throw new ArgumentNullException(nameof(appVersion)); + _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options) + { + WriteIndented = true + }; + + // We need to use the default GUID converter, so we need to remove any custom ones. + for (int a = _jsonOptions.Converters.Count - 1; a >= 0; a--) + { + if (_jsonOptions.Converters[a] is JsonGuidConverter convertor) + { + _jsonOptions.Converters.Remove(convertor); + break; + } + } + + _config = config; + _appHost = appHost; + _minimumVersion = new Version(0, 0, 0, 1); + _plugins = Directory.Exists(_pluginsPath) ? DiscoverPlugins().ToList() : new List<LocalPlugin>(); + + _assemblyLoadContexts = new List<AssemblyLoadContext>(); + } + + private IHttpClientFactory HttpClientFactory + { + get + { + return _httpClientFactory ??= _appHost.Resolve<IHttpClientFactory>(); + } + } + + /// <summary> + /// Gets the Plugins. + /// </summary> + public IReadOnlyList<LocalPlugin> Plugins => _plugins; + + /// <summary> + /// Returns all the assemblies. + /// </summary> + /// <returns>An IEnumerable{Assembly}.</returns> + public IEnumerable<Assembly> LoadAssemblies() + { + // Attempt to remove any deleted plugins and change any successors to be active. + for (int i = _plugins.Count - 1; i >= 0; i--) + { + var plugin = _plugins[i]; + if (plugin.Manifest.Status == PluginStatus.Deleted && DeletePlugin(plugin)) + { + // See if there is another version, and if so make that active. + ProcessAlternative(plugin); + } + } + + // Now load the assemblies.. + foreach (var plugin in _plugins) + { + UpdatePluginSupersededStatus(plugin); + + if (plugin.IsEnabledAndSupported == false) + { + _logger.LogInformation("Skipping disabled plugin {Version} of {Name} ", plugin.Version, plugin.Name); + continue; + } + + var assemblyLoadContext = new PluginLoadContext(plugin.Path); + _assemblyLoadContexts.Add(assemblyLoadContext); + + var assemblies = new List<Assembly>(plugin.DllFiles.Count); + var loadedAll = true; + + foreach (var file in plugin.DllFiles) + { + try + { + assemblies.Add(assemblyLoadContext.LoadFromAssemblyPath(file)); + } + catch (FileLoadException ex) + { + _logger.LogError(ex, "Failed to load assembly {Path}. Disabling plugin", file); + ChangePluginState(plugin, PluginStatus.Malfunctioned); + loadedAll = false; + break; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + _logger.LogError(ex, "Failed to load assembly {Path}. Unknown exception was thrown. Disabling plugin", file); + ChangePluginState(plugin, PluginStatus.Malfunctioned); + loadedAll = false; + break; + } + } + + if (!loadedAll) + { + continue; + } + + foreach (var assembly in assemblies) + { + try + { + // Load all required types to verify that the plugin will load + assembly.GetTypes(); + } + catch (SystemException ex) when (ex is TypeLoadException or ReflectionTypeLoadException) // Undocumented exception + { + _logger.LogError(ex, "Failed to load assembly {Path}. This error occurs when a plugin references an incompatible version of one of the shared libraries. Disabling plugin", assembly.Location); + ChangePluginState(plugin, PluginStatus.NotSupported); + break; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + _logger.LogError(ex, "Failed to load assembly {Path}. Unknown exception was thrown. Disabling plugin", assembly.Location); + ChangePluginState(plugin, PluginStatus.Malfunctioned); + break; + } + + _logger.LogInformation("Loaded assembly {Assembly} from {Path}", assembly.FullName, assembly.Location); + yield return assembly; + } + } + } + + /// <summary> + /// Creates all the plugin instances. + /// </summary> + public void CreatePlugins() + { + _ = _appHost.GetExports<IPlugin>(CreatePluginInstance); + } + + /// <summary> + /// Registers the plugin's services with the DI. + /// Note: DI is not yet instantiated yet. + /// </summary> + /// <param name="serviceCollection">A <see cref="ServiceCollection"/> instance.</param> + public void RegisterServices(IServiceCollection serviceCollection) + { + foreach (var pluginServiceRegistrator in _appHost.GetExportTypes<IPluginServiceRegistrator>()) + { + var plugin = GetPluginByAssembly(pluginServiceRegistrator.Assembly); + if (plugin is null) + { + _logger.LogError("Unable to find plugin in assembly {Assembly}", pluginServiceRegistrator.Assembly.FullName); + continue; + } + + UpdatePluginSupersededStatus(plugin); + if (!plugin.IsEnabledAndSupported) + { + continue; + } + + try + { + var instance = (IPluginServiceRegistrator?)Activator.CreateInstance(pluginServiceRegistrator); + instance?.RegisterServices(serviceCollection, _appHost); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + _logger.LogError(ex, "Error registering plugin services from {Assembly}.", pluginServiceRegistrator.Assembly.FullName); + if (ChangePluginState(plugin, PluginStatus.Malfunctioned)) + { + _logger.LogInformation("Disabling plugin {Path}", plugin.Path); + } + } + } + } + + /// <summary> + /// Imports a plugin manifest from <paramref name="folder"/>. + /// </summary> + /// <param name="folder">Folder of the plugin.</param> + public void ImportPluginFrom(string folder) + { + ArgumentException.ThrowIfNullOrEmpty(folder); + + // Load the plugin. + var plugin = LoadManifest(folder); + // Make sure we haven't already loaded this. + if (_plugins.Any(p => p.Manifest.Equals(plugin.Manifest))) + { + return; + } + + _plugins.Add(plugin); + EnablePlugin(plugin); + } + + /// <summary> + /// Removes the plugin reference '<paramref name="plugin"/>. + /// </summary> + /// <param name="plugin">The plugin.</param> + /// <returns>Outcome of the operation.</returns> + public bool RemovePlugin(LocalPlugin plugin) + { + ArgumentNullException.ThrowIfNull(plugin); + + if (DeletePlugin(plugin)) + { + ProcessAlternative(plugin); + return true; + } + + _logger.LogWarning("Unable to delete {Path}, so marking as deleteOnStartup.", plugin.Path); + // Unable to delete, so disable. + if (ChangePluginState(plugin, PluginStatus.Deleted)) + { + ProcessAlternative(plugin); + return true; + } + + return false; + } + + /// <summary> + /// Attempts to find the plugin with and id of <paramref name="id"/>. + /// </summary> + /// <param name="id">The <see cref="Guid"/> of plugin.</param> + /// <param name="version">Optional <see cref="Version"/> of the plugin to locate.</param> + /// <returns>A <see cref="LocalPlugin"/> if located, or null if not.</returns> + public LocalPlugin? GetPlugin(Guid id, Version? version = null) + { + LocalPlugin? plugin; + + if (version is null) + { + // If no version is given, return the current instance. + var plugins = _plugins.Where(p => p.Id.Equals(id)).ToList(); + + plugin = plugins.FirstOrDefault(p => p.Instance is not null) ?? plugins.MaxBy(p => p.Version); + } + else + { + // Match id and version number. + plugin = _plugins.FirstOrDefault(p => p.Id.Equals(id) && p.Version.Equals(version)); + } + + return plugin; + } + + /// <summary> + /// Enables the plugin, disabling all other versions. + /// </summary> + /// <param name="plugin">The <see cref="LocalPlugin"/> of the plug to disable.</param> + public void EnablePlugin(LocalPlugin plugin) + { + ArgumentNullException.ThrowIfNull(plugin); + + if (ChangePluginState(plugin, PluginStatus.Active)) + { + // See if there is another version, and if so, supercede it. + ProcessAlternative(plugin); + } + } + + /// <summary> + /// Disable the plugin. + /// </summary> + /// <param name="plugin">The <see cref="LocalPlugin"/> of the plug to disable.</param> + public void DisablePlugin(LocalPlugin plugin) + { + ArgumentNullException.ThrowIfNull(plugin); + + // Update the manifest on disk + if (ChangePluginState(plugin, PluginStatus.Disabled)) + { + // If there is another version, activate it. + ProcessAlternative(plugin); + } + } + + /// <summary> + /// Disable the plugin. + /// </summary> + /// <param name="assembly">The <see cref="Assembly"/> of the plug to disable.</param> + public void FailPlugin(Assembly assembly) + { + // Only save if disabled. + ArgumentNullException.ThrowIfNull(assembly); + + var plugin = _plugins.FirstOrDefault(p => p.DllFiles.Contains(assembly.Location)); + if (plugin is null) + { + // A plugin's assembly didn't cause this issue, so ignore it. + return; + } + + ChangePluginState(plugin, PluginStatus.Malfunctioned); + } + + /// <inheritdoc/> + public bool SaveManifest(PluginManifest manifest, string path) + { + try + { + var data = JsonSerializer.Serialize(manifest, _jsonOptions); + File.WriteAllText(Path.Combine(path, MetafileName), data); + return true; + } + catch (ArgumentException e) + { + _logger.LogWarning(e, "Unable to save plugin manifest due to invalid value. {Path}", path); + return false; + } + } + + /// <inheritdoc/> + public async Task<bool> PopulateManifest(PackageInfo packageInfo, Version version, string path, PluginStatus status) + { + var versionInfo = packageInfo.Versions.First(v => v.Version == version.ToString()); + var imagePath = string.Empty; + + if (!string.IsNullOrEmpty(packageInfo.ImageUrl)) + { + var url = new Uri(packageInfo.ImageUrl); + imagePath = Path.Join(path, url.Segments[^1]); + + var fileStream = AsyncFile.OpenWrite(imagePath); + Stream? downloadStream = null; + try + { + downloadStream = await HttpClientFactory + .CreateClient(NamedClient.Default) + .GetStreamAsync(url) + .ConfigureAwait(false); + + await downloadStream.CopyToAsync(fileStream).ConfigureAwait(false); + } + catch (HttpRequestException ex) + { + _logger.LogError(ex, "Failed to download image to path {Path} on disk.", imagePath); + imagePath = string.Empty; + } + finally + { + await fileStream.DisposeAsync().ConfigureAwait(false); + if (downloadStream is not null) + { + await downloadStream.DisposeAsync().ConfigureAwait(false); + } + } + } + + var manifest = new PluginManifest + { + Category = packageInfo.Category, + Changelog = versionInfo.Changelog ?? string.Empty, + Description = packageInfo.Description, + Id = packageInfo.Id, + Name = packageInfo.Name, + Overview = packageInfo.Overview, + Owner = packageInfo.Owner, + TargetAbi = versionInfo.TargetAbi ?? string.Empty, + Timestamp = string.IsNullOrEmpty(versionInfo.Timestamp) ? DateTime.MinValue : DateTime.Parse(versionInfo.Timestamp, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal), + Version = versionInfo.Version, + Status = status == PluginStatus.Disabled ? PluginStatus.Disabled : PluginStatus.Active, // Keep disabled state. + AutoUpdate = true, + ImagePath = imagePath + }; + + if (!await ReconcileManifest(manifest, path).ConfigureAwait(false)) + { + // An error occurred during reconciliation and saving could be undesirable. + return false; + } + + return SaveManifest(manifest, path); + } + + /// <inheritdoc /> + public void Dispose() + { + foreach (var assemblyLoadContext in _assemblyLoadContexts) + { + assemblyLoadContext.Unload(); + } + } + + /// <summary> + /// Reconciles the manifest against any properties that exist locally in a pre-packaged meta.json found at the path. + /// If no file is found, no reconciliation occurs. + /// </summary> + /// <param name="manifest">The <see cref="PluginManifest"/> to reconcile against.</param> + /// <param name="path">The plugin path.</param> + /// <returns>The reconciled <see cref="PluginManifest"/>.</returns> + private async Task<bool> ReconcileManifest(PluginManifest manifest, string path) + { + try + { + var metafile = Path.Combine(path, MetafileName); + if (!File.Exists(metafile)) + { + _logger.LogInformation("No local manifest exists for plugin {Plugin}. Skipping manifest reconciliation.", manifest.Name); + return true; + } + + using var metaStream = File.OpenRead(metafile); + var localManifest = await JsonSerializer.DeserializeAsync<PluginManifest>(metaStream, _jsonOptions).ConfigureAwait(false); + localManifest ??= new PluginManifest(); + + if (!Equals(localManifest.Id, manifest.Id)) + { + _logger.LogError("The manifest ID {LocalUUID} did not match the package info ID {PackageUUID}.", localManifest.Id, manifest.Id); + manifest.Status = PluginStatus.Malfunctioned; + } + + if (localManifest.Version != manifest.Version) + { + // Package information provides the version and is the source of truth. Pre-packages meta.json is assumed to be a mistake in this regard. + _logger.LogWarning("The version of the local manifest was {LocalVersion}, but {PackageVersion} was expected. The value will be replaced.", localManifest.Version, manifest.Version); + } + + // Explicitly mapping properties instead of using reflection is preferred here. + manifest.Category = string.IsNullOrEmpty(localManifest.Category) ? manifest.Category : localManifest.Category; + manifest.AutoUpdate = localManifest.AutoUpdate; // Preserve whatever is local. Package info does not have this property. + manifest.Changelog = string.IsNullOrEmpty(localManifest.Changelog) ? manifest.Changelog : localManifest.Changelog; + manifest.Description = string.IsNullOrEmpty(localManifest.Description) ? manifest.Description : localManifest.Description; + manifest.Name = string.IsNullOrEmpty(localManifest.Name) ? manifest.Name : localManifest.Name; + manifest.Overview = string.IsNullOrEmpty(localManifest.Overview) ? manifest.Overview : localManifest.Overview; + manifest.Owner = string.IsNullOrEmpty(localManifest.Owner) ? manifest.Owner : localManifest.Owner; + manifest.TargetAbi = string.IsNullOrEmpty(localManifest.TargetAbi) ? manifest.TargetAbi : localManifest.TargetAbi; + manifest.Timestamp = localManifest.Timestamp.Equals(default) ? manifest.Timestamp : localManifest.Timestamp; + manifest.ImagePath = string.IsNullOrEmpty(localManifest.ImagePath) ? manifest.ImagePath : localManifest.ImagePath; + manifest.Assemblies = localManifest.Assemblies; + + return true; + } + catch (Exception e) + { + _logger.LogWarning(e, "Unable to reconcile plugin manifest due to an error. {Path}", path); + return false; + } + } + + /// <summary> + /// Changes a plugin's load status. + /// </summary> + /// <param name="plugin">The <see cref="LocalPlugin"/> instance.</param> + /// <param name="state">The <see cref="PluginStatus"/> of the plugin.</param> + /// <returns>Success of the task.</returns> + private bool ChangePluginState(LocalPlugin plugin, PluginStatus state) + { + if (plugin.Manifest.Status == state || string.IsNullOrEmpty(plugin.Path)) + { + // No need to save as the state hasn't changed. + return true; + } + + plugin.Manifest.Status = state; + return SaveManifest(plugin.Manifest, plugin.Path); + } + + /// <summary> + /// Finds the plugin record using the assembly. + /// </summary> + /// <param name="assembly">The <see cref="Assembly"/> being sought.</param> + /// <returns>The matching record, or null if not found.</returns> + private LocalPlugin? GetPluginByAssembly(Assembly assembly) + { + // Find which plugin it is by the path. + return _plugins.FirstOrDefault(p => p.DllFiles.Contains(assembly.Location, StringComparer.Ordinal)); + } + + /// <summary> + /// Creates the instance safe. + /// </summary> + /// <param name="type">The type.</param> + /// <returns>System.Object.</returns> + private IPlugin? CreatePluginInstance(Type type) + { + // Find the record for this plugin. + var plugin = GetPluginByAssembly(type.Assembly); + if (plugin?.Manifest.Status < PluginStatus.Active) + { + return null; + } + + try + { + _logger.LogDebug("Creating instance of {Type}", type); + // _appHost.ServiceProvider is already assigned when we create the plugins + var instance = (IPlugin)ActivatorUtilities.CreateInstance(_appHost.ServiceProvider!, type); + if (plugin is null) + { + // Create a dummy record for the providers. + // TODO: remove this code once all provided have been released as separate plugins. + plugin = new LocalPlugin( + instance.AssemblyFilePath, + true, + new PluginManifest + { + Id = instance.Id, + Status = PluginStatus.Active, + Name = instance.Name, + Version = instance.Version.ToString() + }) + { + Instance = instance + }; + + _plugins.Add(plugin); + + plugin.Manifest.Status = PluginStatus.Active; + } + else + { + plugin.Instance = instance; + var manifest = plugin.Manifest; + var pluginStr = instance.Version.ToString(); + bool changed = false; + if (string.Equals(manifest.Version, pluginStr, StringComparison.Ordinal) + || !manifest.Id.Equals(instance.Id)) + { + // If a plugin without a manifest failed to load due to an external issue (eg config), + // this updates the manifest to the actual plugin values. + manifest.Version = pluginStr; + manifest.Name = plugin.Instance.Name; + manifest.Description = plugin.Instance.Description; + manifest.Id = plugin.Instance.Id; + changed = true; + } + + changed = changed || manifest.Status != PluginStatus.Active; + manifest.Status = PluginStatus.Active; + + if (changed) + { + SaveManifest(manifest, plugin.Path); + } + } + + _logger.LogInformation("Loaded plugin: {PluginName} {PluginVersion}", plugin.Name, plugin.Version); + + return instance; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + _logger.LogError(ex, "Error creating {Type}", type.FullName); + if (plugin is not null) + { + if (ChangePluginState(plugin, PluginStatus.Malfunctioned)) + { + _logger.LogInformation("Plugin {Path} has been disabled.", plugin.Path); + return null; + } + } + + _logger.LogDebug("Unable to auto-disable."); + return null; + } + } + + private void UpdatePluginSupersededStatus(LocalPlugin plugin) + { + if (plugin.Manifest.Status != PluginStatus.Superseded) + { + return; + } + + var predecessor = _plugins.OrderByDescending(p => p.Version) + .FirstOrDefault(p => p.Id.Equals(plugin.Id) && p.IsEnabledAndSupported && p.Version != plugin.Version); + if (predecessor is not null) + { + return; + } + + plugin.Manifest.Status = PluginStatus.Active; + } + + /// <summary> + /// Attempts to delete a plugin. + /// </summary> + /// <param name="plugin">A <see cref="LocalPlugin"/> instance to delete.</param> + /// <returns>True if successful.</returns> + private bool DeletePlugin(LocalPlugin plugin) + { + // Attempt a cleanup of old folders. + try + { + Directory.Delete(plugin.Path, true); + _logger.LogDebug("Deleted {Path}", plugin.Path); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch +#pragma warning restore CA1031 // Do not catch general exception types + { + return false; + } + + return _plugins.Remove(plugin); + } + + internal LocalPlugin LoadManifest(string dir) + { + Version? version; + PluginManifest? manifest = null; + var metafile = Path.Combine(dir, MetafileName); + if (File.Exists(metafile)) + { + // Only path where this stays null is when File.ReadAllBytes throws an IOException + byte[] data = null!; + try + { + data = File.ReadAllBytes(metafile); + manifest = JsonSerializer.Deserialize<PluginManifest>(data, _jsonOptions); + } + catch (IOException ex) + { + _logger.LogError(ex, "Error reading file {Path}.", dir); + } + catch (JsonException ex) + { + _logger.LogError(ex, "Error deserializing {Json}.", Encoding.UTF8.GetString(data)); + } + + if (manifest is not null) + { + if (!Version.TryParse(manifest.TargetAbi, out var targetAbi)) + { + targetAbi = _minimumVersion; + } + + if (!Version.TryParse(manifest.Version, out version)) + { + manifest.Version = _minimumVersion.ToString(); + } + + return new LocalPlugin(dir, _appVersion >= targetAbi, manifest); + } + } + + // No metafile, so lets see if the folder is versioned. + // TODO: Phase this support out in future versions. + metafile = dir.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries)[^1]; + int versionIndex = dir.LastIndexOf('_'); + if (versionIndex != -1) + { + // Get the version number from the filename if possible. + metafile = Path.GetFileName(dir[..versionIndex]); + version = Version.TryParse(dir.AsSpan()[(versionIndex + 1)..], out Version? parsedVersion) ? parsedVersion : _appVersion; + } + else + { + // Un-versioned folder - Add it under the path name and version it suitable for this instance. + version = _appVersion; + } + + // Auto-create a plugin manifest, so we can disable it, if it fails to load. + manifest = new PluginManifest + { + Status = PluginStatus.Active, + Name = metafile, + AutoUpdate = false, + Id = metafile.GetMD5(), + TargetAbi = _appVersion.ToString(), + Version = version.ToString() + }; + + return new LocalPlugin(dir, true, manifest); + } + + /// <summary> + /// Gets the list of local plugins. + /// </summary> + /// <returns>Enumerable of local plugins.</returns> + private IEnumerable<LocalPlugin> DiscoverPlugins() + { + var versions = new List<LocalPlugin>(); + + if (!Directory.Exists(_pluginsPath)) + { + // Plugin path doesn't exist, don't try to enumerate sub-folders. + return Enumerable.Empty<LocalPlugin>(); + } + + var directories = Directory.EnumerateDirectories(_pluginsPath, "*.*", SearchOption.TopDirectoryOnly); + foreach (var dir in directories) + { + versions.Add(LoadManifest(dir)); + } + + string lastName = string.Empty; + versions.Sort(LocalPlugin.Compare); + // Traverse backwards through the list. + // The first item will be the latest version. + for (int x = versions.Count - 1; x >= 0; x--) + { + var entry = versions[x]; + if (!string.Equals(lastName, entry.Name, StringComparison.OrdinalIgnoreCase)) + { + if (!TryGetPluginDlls(entry, out var allowedDlls)) + { + _logger.LogError("One or more assembly paths was invalid. Marking plugin {Plugin} as \"Malfunctioned\".", entry.Name); + ChangePluginState(entry, PluginStatus.Malfunctioned); + continue; + } + + entry.DllFiles = allowedDlls; + + if (entry.IsEnabledAndSupported) + { + lastName = entry.Name; + continue; + } + } + + if (string.IsNullOrEmpty(lastName)) + { + continue; + } + + var cleaned = false; + var path = entry.Path; + // Attempt a cleanup of old folders. + try + { + _logger.LogDebug("Deleting {Path}", path); + Directory.Delete(path, true); + cleaned = true; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception e) +#pragma warning restore CA1031 // Do not catch general exception types + { + _logger.LogWarning(e, "Unable to delete {Path}", path); + } + + if (cleaned) + { + versions.RemoveAt(x); + } + else + { + ChangePluginState(entry, PluginStatus.Deleted); + } + } + + // Only want plugin folders which have files. + return versions.Where(p => p.DllFiles.Count != 0); + } + + /// <summary> + /// Attempts to retrieve valid DLLs from the plugin path. This method will consider the assembly whitelist + /// from the manifest. + /// </summary> + /// <remarks> + /// Loading DLLs from externally supplied paths introduces a path traversal risk. This method + /// uses a safelisting tactic of considering DLLs from the plugin directory and only using + /// the plugin's canonicalized assembly whitelist for comparison. See + /// <see href="https://owasp.org/www-community/attacks/Path_Traversal"/> for more details. + /// </remarks> + /// <param name="plugin">The plugin.</param> + /// <param name="whitelistedDlls">The whitelisted DLLs. If the method returns <see langword="false"/>, this will be empty.</param> + /// <returns> + /// <see langword="true"/> if all assemblies listed in the manifest were available in the plugin directory. + /// <see langword="false"/> if any assemblies were invalid or missing from the plugin directory. + /// </returns> + /// <exception cref="ArgumentNullException">If the <see cref="LocalPlugin"/> is null.</exception> + private bool TryGetPluginDlls(LocalPlugin plugin, out IReadOnlyList<string> whitelistedDlls) + { + ArgumentNullException.ThrowIfNull(plugin); + + IReadOnlyList<string> pluginDlls = Directory.GetFiles(plugin.Path, "*.dll", SearchOption.AllDirectories); + + whitelistedDlls = Array.Empty<string>(); + if (pluginDlls.Count > 0 && plugin.Manifest.Assemblies.Count > 0) + { + _logger.LogInformation("Registering whitelisted assemblies for plugin \"{Plugin}\"...", plugin.Name); + + var canonicalizedPaths = new List<string>(); + foreach (var path in plugin.Manifest.Assemblies) + { + var canonicalized = Path.Combine(plugin.Path, path).Canonicalize(); + + // Ensure we stay in the plugin directory. + if (!canonicalized.StartsWith(plugin.Path.NormalizePath(), StringComparison.Ordinal)) + { + _logger.LogError("Assembly path {Path} is not inside the plugin directory.", path); + return false; + } + + canonicalizedPaths.Add(canonicalized); + } + + var intersected = pluginDlls.Intersect(canonicalizedPaths).ToList(); + + if (intersected.Count != canonicalizedPaths.Count) + { + _logger.LogError("Plugin {Plugin} contained assembly paths that were not found in the directory.", plugin.Name); + return false; + } + + whitelistedDlls = intersected; + } + else + { + // No whitelist, default to loading all DLLs in plugin directory. + whitelistedDlls = pluginDlls; + } + + return true; + } + + /// <summary> + /// Changes the status of the other versions of the plugin to "Superseded". + /// </summary> + /// <param name="plugin">The <see cref="LocalPlugin"/> that's master.</param> + private void ProcessAlternative(LocalPlugin plugin) + { + // Detect whether there is another version of this plugin that needs disabling. + var previousVersion = _plugins.OrderByDescending(p => p.Version) + .FirstOrDefault( + p => p.Id.Equals(plugin.Id) + && p.IsEnabledAndSupported + && p.Version != plugin.Version); + + if (previousVersion is null) + { + // This value is memory only - so that the web will show restart required. + plugin.Manifest.Status = PluginStatus.Restart; + plugin.Manifest.AutoUpdate = false; + return; + } + + if (plugin.Manifest.Status == PluginStatus.Active && !ChangePluginState(previousVersion, PluginStatus.Superseded)) + { + _logger.LogError("Unable to enable version {Version} of {Name}", previousVersion.Version, previousVersion.Name); + } + else if (plugin.Manifest.Status == PluginStatus.Superseded && !ChangePluginState(previousVersion, PluginStatus.Active)) + { + _logger.LogError("Unable to supercede version {Version} of {Name}", previousVersion.Version, previousVersion.Name); + } + + // This value is memory only - so that the web will show restart required. + plugin.Manifest.Status = PluginStatus.Restart; + plugin.Manifest.AutoUpdate = false; + } + } +} diff --git a/Emby.Server.Implementations/Properties/AssemblyInfo.cs b/Emby.Server.Implementations/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..41c396ac --- /dev/null +++ b/Emby.Server.Implementations/Properties/AssemblyInfo.cs @@ -0,0 +1,24 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Emby.Server.Implementations")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Jellyfin Project")] +[assembly: AssemblyProduct("Jellyfin Server")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] +[assembly: InternalsVisibleTo("Jellyfin.Server.Implementations.Tests")] +[assembly: InternalsVisibleTo("Emby.Server.Implementations.Fuzz")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs new file mode 100644 index 00000000..c4bda963 --- /dev/null +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections.Concurrent; +using System.Globalization; +using System.Linq; +using System.Security.Cryptography; +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.QuickConnect; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.QuickConnect; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.QuickConnect +{ + /// <summary> + /// Quick connect implementation. + /// </summary> + public class QuickConnectManager : IQuickConnect + { + /// <summary> + /// The length of user facing codes. + /// </summary> + private const int CodeLength = 6; + + /// <summary> + /// The time (in minutes) that the quick connect token is valid. + /// </summary> + private const int Timeout = 10; + + private readonly ConcurrentDictionary<string, QuickConnectResult> _currentRequests = new(); + private readonly ConcurrentDictionary<string, (DateTime Timestamp, AuthenticationResult AuthenticationResult)> _authorizedSecrets = new(); + + private readonly IServerConfigurationManager _config; + private readonly ILogger<QuickConnectManager> _logger; + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="QuickConnectManager"/> class. + /// Should only be called at server startup when a singleton is created. + /// </summary> + /// <param name="config">Configuration.</param> + /// <param name="logger">Logger.</param> + /// <param name="sessionManager">Session Manager.</param> + public QuickConnectManager( + IServerConfigurationManager config, + ILogger<QuickConnectManager> logger, + ISessionManager sessionManager) + { + _config = config; + _logger = logger; + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public bool IsEnabled => _config.Configuration.QuickConnectAvailable; + + /// <summary> + /// Assert that quick connect is currently active and throws an exception if it is not. + /// </summary> + private void AssertActive() + { + if (!IsEnabled) + { + throw new AuthenticationException("Quick connect is not active on this server"); + } + } + + /// <inheritdoc/> + public QuickConnectResult TryConnect(AuthorizationInfo authorizationInfo) + { + ArgumentException.ThrowIfNullOrEmpty(authorizationInfo.DeviceId); + ArgumentException.ThrowIfNullOrEmpty(authorizationInfo.Device); + ArgumentException.ThrowIfNullOrEmpty(authorizationInfo.Client); + ArgumentException.ThrowIfNullOrEmpty(authorizationInfo.Version); + + AssertActive(); + ExpireRequests(); + + var secret = GenerateSecureRandom(); + var code = GenerateCode(); + var result = new QuickConnectResult( + secret, + code, + DateTime.UtcNow, + authorizationInfo.DeviceId, + authorizationInfo.Device, + authorizationInfo.Client, + authorizationInfo.Version); + + _currentRequests[code] = result; + return result; + } + + /// <inheritdoc/> + public QuickConnectResult CheckRequestStatus(string secret) + { + AssertActive(); + ExpireRequests(); + + string code = _currentRequests.Where(x => x.Value.Secret == secret).Select(x => x.Value.Code).DefaultIfEmpty(string.Empty).First(); + + if (!_currentRequests.TryGetValue(code, out QuickConnectResult? result)) + { + throw new ResourceNotFoundException("Unable to find request with provided secret"); + } + + return result; + } + + /// <summary> + /// Generates a short code to display to the user to uniquely identify this request. + /// </summary> + /// <returns>A short, unique alphanumeric string.</returns> + private string GenerateCode() + { + Span<byte> raw = stackalloc byte[4]; + + int min = (int)Math.Pow(10, CodeLength - 1); + int max = (int)Math.Pow(10, CodeLength); + + uint scale = uint.MaxValue; + while (scale == uint.MaxValue) + { + RandomNumberGenerator.Fill(raw); + scale = BitConverter.ToUInt32(raw); + } + + int code = (int)(min + ((max - min) * (scale / (double)uint.MaxValue))); + return code.ToString(CultureInfo.InvariantCulture); + } + + /// <inheritdoc/> + public async Task<bool> AuthorizeRequest(Guid userId, string code) + { + AssertActive(); + ExpireRequests(); + + if (!_currentRequests.TryGetValue(code, out QuickConnectResult? result)) + { + throw new ResourceNotFoundException("Unable to find request"); + } + + if (result.Authenticated) + { + throw new InvalidOperationException("Request is already authorized"); + } + + // Change the time on the request so it expires one minute into the future. It can't expire immediately as otherwise some clients wouldn't ever see that they have been authenticated. + result.DateAdded = DateTime.UtcNow.Add(TimeSpan.FromMinutes(1)); + + var authenticationResult = await _sessionManager.AuthenticateDirect(new AuthenticationRequest + { + UserId = userId, + DeviceId = result.DeviceId, + DeviceName = result.DeviceName, + App = result.AppName, + AppVersion = result.AppVersion + }).ConfigureAwait(false); + + _authorizedSecrets[result.Secret] = (DateTime.UtcNow, authenticationResult); + result.Authenticated = true; + _currentRequests[code] = result; + + _logger.LogDebug("Authorizing device with code {Code} to login as user {UserId}", code, userId); + + return true; + } + + /// <inheritdoc/> + public AuthenticationResult GetAuthorizedRequest(string secret) + { + AssertActive(); + ExpireRequests(); + + if (!_authorizedSecrets.TryGetValue(secret, out var result)) + { + throw new ResourceNotFoundException("Unable to find request"); + } + + return result.AuthenticationResult; + } + + private string GenerateSecureRandom(int length = 32) + { + Span<byte> bytes = stackalloc byte[length]; + RandomNumberGenerator.Fill(bytes); + + return Convert.ToHexString(bytes); + } + + /// <summary> + /// Expire quick connect requests that are over the time limit. If <paramref name="expireAll"/> is true, all requests are unconditionally expired. + /// </summary> + /// <param name="expireAll">If true, all requests will be expired.</param> + private void ExpireRequests(bool expireAll = false) + { + // All requests before this timestamp have expired + var minTime = DateTime.UtcNow.AddMinutes(-Timeout); + + // Expire stale connection requests + foreach (var (_, currentRequest) in _currentRequests) + { + if (expireAll || currentRequest.DateAdded < minTime) + { + var code = currentRequest.Code; + _logger.LogDebug("Removing expired request {Code}", code); + + if (!_currentRequests.TryRemove(code, out _)) + { + _logger.LogWarning("Request {Code} already expired", code); + } + } + } + + foreach (var (secret, (timestamp, _)) in _authorizedSecrets) + { + if (expireAll || timestamp < minTime) + { + _logger.LogDebug("Removing expired secret {Secret}", secret); + if (!_authorizedSecrets.TryRemove(secret, out _)) + { + _logger.LogWarning("Secret {Secret} already expired", secret); + } + } + } + } + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs new file mode 100644 index 00000000..24f55498 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -0,0 +1,678 @@ +#nullable disable + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Implementations.ScheduledTasks.Triggers; +using Jellyfin.Data.Events; +using Jellyfin.Extensions.Json; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks; + +/// <summary> +/// Class ScheduledTaskWorker. +/// </summary> +public class ScheduledTaskWorker : IScheduledTaskWorker +{ + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + private readonly IApplicationPaths _applicationPaths; + private readonly ILogger _logger; + private readonly ITaskManager _taskManager; + private readonly Lock _lastExecutionResultSyncLock = new(); + private bool _readFromFile; + private TaskResult _lastExecutionResult; + private Task _currentTask; + private Tuple<TaskTriggerInfo, ITaskTrigger>[] _triggers; + private string _id; + + /// <summary> + /// Initializes a new instance of the <see cref="ScheduledTaskWorker" /> class. + /// </summary> + /// <param name="scheduledTask">The scheduled task.</param> + /// <param name="applicationPaths">The application paths.</param> + /// <param name="taskManager">The task manager.</param> + /// <param name="logger">The logger.</param> + /// <exception cref="ArgumentNullException"> + /// scheduledTask + /// or + /// applicationPaths + /// or + /// taskManager + /// or + /// jsonSerializer + /// or + /// logger. + /// </exception> + public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, ILogger logger) + { + ArgumentNullException.ThrowIfNull(scheduledTask); + ArgumentNullException.ThrowIfNull(applicationPaths); + ArgumentNullException.ThrowIfNull(taskManager); + ArgumentNullException.ThrowIfNull(logger); + + ScheduledTask = scheduledTask; + _applicationPaths = applicationPaths; + _taskManager = taskManager; + _logger = logger; + + InitTriggerEvents(); + } + + /// <inheritdoc /> + public event EventHandler<GenericEventArgs<double>> TaskProgress; + + /// <inheritdoc /> + public IScheduledTask ScheduledTask { get; private set; } + + /// <inheritdoc /> + public TaskResult LastExecutionResult + { + get + { + var path = GetHistoryFilePath(); + + lock (_lastExecutionResultSyncLock) + { + if (_lastExecutionResult is null && !_readFromFile) + { + if (File.Exists(path)) + { + var bytes = File.ReadAllBytes(path); + if (bytes.Length > 0) + { + try + { + _lastExecutionResult = JsonSerializer.Deserialize<TaskResult>(bytes, _jsonOptions); + } + catch (JsonException ex) + { + _logger.LogError(ex, "Error deserializing {File}", path); + } + } + else + { + _logger.LogDebug("Scheduled Task history file {Path} is empty. Skipping deserialization.", path); + } + } + + _readFromFile = true; + } + } + + return _lastExecutionResult; + } + + private set + { + _lastExecutionResult = value; + + var path = GetHistoryFilePath(); + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + lock (_lastExecutionResultSyncLock) + { + using FileStream createStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None); + using Utf8JsonWriter jsonStream = new Utf8JsonWriter(createStream); + JsonSerializer.Serialize(jsonStream, value, _jsonOptions); + } + } + } + + /// <inheritdoc /> + public string Name => ScheduledTask.Name; + + /// <inheritdoc /> + public string Description => ScheduledTask.Description; + + /// <inheritdoc /> + public string Category => ScheduledTask.Category; + + /// <summary> + /// Gets or sets the current cancellation token. + /// </summary> + /// <value>The current cancellation token source.</value> + private CancellationTokenSource CurrentCancellationTokenSource { get; set; } + + /// <summary> + /// Gets or sets the current execution start time. + /// </summary> + /// <value>The current execution start time.</value> + private DateTime CurrentExecutionStartTime { get; set; } + + /// <inheritdoc /> + public TaskState State + { + get + { + if (CurrentCancellationTokenSource is not null) + { + return CurrentCancellationTokenSource.IsCancellationRequested + ? TaskState.Cancelling + : TaskState.Running; + } + + return TaskState.Idle; + } + } + + /// <inheritdoc /> + public double? CurrentProgress { get; private set; } + + /// <summary> + /// Gets or sets the triggers that define when the task will run. + /// </summary> + /// <value>The triggers.</value> + private Tuple<TaskTriggerInfo, ITaskTrigger>[] InternalTriggers + { + get => _triggers; + set + { + ArgumentNullException.ThrowIfNull(value); + + // Cleanup current triggers + if (_triggers is not null) + { + DisposeTriggers(); + } + + _triggers = value.ToArray(); + + ReloadTriggerEvents(false); + } + } + + /// <inheritdoc /> + public IReadOnlyList<TaskTriggerInfo> Triggers + { + get + { + return Array.ConvertAll(InternalTriggers, i => i.Item1); + } + + set + { + ArgumentNullException.ThrowIfNull(value); + + // This null check is not great, but is needed to handle bad user input, or user mucking with the config file incorrectly + var triggerList = value.Where(i => i is not null).ToArray(); + + SaveTriggers(triggerList); + + InternalTriggers = Array.ConvertAll(triggerList, i => new Tuple<TaskTriggerInfo, ITaskTrigger>(i, GetTrigger(i))); + } + } + + /// <inheritdoc /> + public string Id + { + get + { + return _id ??= ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture); + } + } + + private void InitTriggerEvents() + { + _triggers = LoadTriggers(); + ReloadTriggerEvents(true); + } + + /// <inheritdoc /> + public void ReloadTriggerEvents() + { + ReloadTriggerEvents(false); + } + + /// <summary> + /// Reloads the trigger events. + /// </summary> + /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param> + private void ReloadTriggerEvents(bool isApplicationStartup) + { + foreach (var triggerInfo in InternalTriggers) + { + var trigger = triggerInfo.Item2; + + trigger.Stop(); + + trigger.Triggered -= OnTriggerTriggered; + trigger.Triggered += OnTriggerTriggered; + trigger.Start(LastExecutionResult, _logger, Name, isApplicationStartup); + } + } + + /// <summary> + /// Handles the Triggered event of the trigger control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> + private async void OnTriggerTriggered(object sender, EventArgs e) + { + var trigger = (ITaskTrigger)sender; + + if (ScheduledTask is IConfigurableScheduledTask configurableTask && !configurableTask.IsEnabled) + { + return; + } + + _logger.LogDebug("{0} fired for task: {1}", trigger.GetType().Name, Name); + + trigger.Stop(); + + _taskManager.QueueScheduledTask(ScheduledTask, trigger.TaskOptions); + + await Task.Delay(1000).ConfigureAwait(false); + + trigger.Start(LastExecutionResult, _logger, Name, false); + } + + /// <summary> + /// Executes the task. + /// </summary> + /// <param name="options">Task options.</param> + /// <returns>Task.</returns> + /// <exception cref="InvalidOperationException">Cannot execute a Task that is already running.</exception> + public async Task Execute(TaskOptions options) + { + var task = Task.Run(async () => await ExecuteInternal(options).ConfigureAwait(false)); + + _currentTask = task; + + try + { + await task.ConfigureAwait(false); + } + finally + { + _currentTask = null; + GC.Collect(); + } + } + + private async Task ExecuteInternal(TaskOptions options) + { + // Cancel the current execution, if any + if (CurrentCancellationTokenSource is not null) + { + throw new InvalidOperationException("Cannot execute a Task that is already running"); + } + + var progress = new Progress<double>(); + + CurrentCancellationTokenSource = new CancellationTokenSource(); + + _logger.LogDebug("Executing {0}", Name); + + ((TaskManager)_taskManager).OnTaskExecuting(this); + + progress.ProgressChanged += OnProgressChanged; + + TaskCompletionStatus status; + CurrentExecutionStartTime = DateTime.UtcNow; + + Exception failureException = null; + + try + { + if (options is not null && options.MaxRuntimeTicks.HasValue) + { + CurrentCancellationTokenSource.CancelAfter(TimeSpan.FromTicks(options.MaxRuntimeTicks.Value)); + } + + await ScheduledTask.ExecuteAsync(progress, CurrentCancellationTokenSource.Token).ConfigureAwait(false); + + status = TaskCompletionStatus.Completed; + } + catch (OperationCanceledException) + { + status = TaskCompletionStatus.Cancelled; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error executing Scheduled Task"); + + failureException = ex; + + status = TaskCompletionStatus.Failed; + } + + var startTime = CurrentExecutionStartTime; + var endTime = DateTime.UtcNow; + + progress.ProgressChanged -= OnProgressChanged; + CurrentCancellationTokenSource.Dispose(); + CurrentCancellationTokenSource = null; + CurrentProgress = null; + + OnTaskCompleted(startTime, endTime, status, failureException); + } + + /// <summary> + /// Progress_s the progress changed. + /// </summary> + /// <param name="sender">The sender.</param> + /// <param name="e">The e.</param> + private void OnProgressChanged(object sender, double e) + { + e = Math.Min(e, 100); + + CurrentProgress = e; + + TaskProgress?.Invoke(this, new GenericEventArgs<double>(e)); + } + + /// <summary> + /// Stops the task if it is currently executing. + /// </summary> + /// <exception cref="InvalidOperationException">Cannot cancel a Task unless it is in the Running state.</exception> + public void Cancel() + { + if (State != TaskState.Running) + { + throw new InvalidOperationException("Cannot cancel a Task unless it is in the Running state."); + } + + CancelIfRunning(); + } + + /// <summary> + /// Cancels if running. + /// </summary> + public void CancelIfRunning() + { + if (State == TaskState.Running) + { + _logger.LogInformation("Attempting to cancel Scheduled Task {0}", Name); + CurrentCancellationTokenSource.Cancel(); + } + } + + /// <summary> + /// Gets the scheduled tasks configuration directory. + /// </summary> + /// <returns>System.String.</returns> + private string GetScheduledTasksConfigurationDirectory() + { + return Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "ScheduledTasks"); + } + + /// <summary> + /// Gets the scheduled tasks data directory. + /// </summary> + /// <returns>System.String.</returns> + private string GetScheduledTasksDataDirectory() + { + return Path.Combine(_applicationPaths.DataPath, "ScheduledTasks"); + } + + /// <summary> + /// Gets the history file path. + /// </summary> + /// <value>The history file path.</value> + private string GetHistoryFilePath() + { + return Path.Combine(GetScheduledTasksDataDirectory(), new Guid(Id) + ".js"); + } + + /// <summary> + /// Gets the configuration file path. + /// </summary> + /// <returns>System.String.</returns> + private string GetConfigurationFilePath() + { + return Path.Combine(GetScheduledTasksConfigurationDirectory(), new Guid(Id) + ".js"); + } + + /// <summary> + /// Loads the triggers. + /// </summary> + /// <returns>IEnumerable{BaseTaskTrigger}.</returns> + private Tuple<TaskTriggerInfo, ITaskTrigger>[] LoadTriggers() + { + // This null check is not great, but is needed to handle bad user input, or user mucking with the config file incorrectly + var settings = LoadTriggerSettings().Where(i => i is not null); + + return settings.Select(i => new Tuple<TaskTriggerInfo, ITaskTrigger>(i, GetTrigger(i))).ToArray(); + } + + private TaskTriggerInfo[] LoadTriggerSettings() + { + string path = GetConfigurationFilePath(); + TaskTriggerInfo[] list = null; + if (File.Exists(path)) + { + var bytes = File.ReadAllBytes(path); + list = JsonSerializer.Deserialize<TaskTriggerInfo[]>(bytes, _jsonOptions); + } + + // Return defaults if file doesn't exist. + return list ?? GetDefaultTriggers(); + } + + private TaskTriggerInfo[] GetDefaultTriggers() + { + try + { + return ScheduledTask.GetDefaultTriggers().ToArray(); + } + catch + { + return + [ + new() + { + IntervalTicks = TimeSpan.FromDays(1).Ticks, + Type = TaskTriggerInfoType.IntervalTrigger + } + ]; + } + } + + /// <summary> + /// Saves the triggers. + /// </summary> + /// <param name="triggers">The triggers.</param> + private void SaveTriggers(TaskTriggerInfo[] triggers) + { + var path = GetConfigurationFilePath(); + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + using FileStream createStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None); + using Utf8JsonWriter jsonWriter = new Utf8JsonWriter(createStream); + JsonSerializer.Serialize(jsonWriter, triggers, _jsonOptions); + } + + /// <summary> + /// Called when [task completed]. + /// </summary> + /// <param name="startTime">The start time.</param> + /// <param name="endTime">The end time.</param> + /// <param name="status">The status.</param> + /// <param name="ex">The exception.</param> + private void OnTaskCompleted(DateTime startTime, DateTime endTime, TaskCompletionStatus status, Exception ex) + { + var elapsedTime = endTime - startTime; + + _logger.LogInformation("{0} {1} after {2} minute(s) and {3} seconds", Name, status, Math.Truncate(elapsedTime.TotalMinutes), elapsedTime.Seconds); + + var result = new TaskResult + { + StartTimeUtc = startTime, + EndTimeUtc = endTime, + Status = status, + Name = Name, + Id = Id + }; + + result.Key = ScheduledTask.Key; + + if (ex is not null) + { + result.ErrorMessage = ex.Message; + result.LongErrorMessage = ex.StackTrace; + } + + LastExecutionResult = result; + + ((TaskManager)_taskManager).OnTaskCompleted(this, result); + } + + /// <inheritdoc /> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> + /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + DisposeTriggers(); + + var wasRunning = State == TaskState.Running; + var startTime = CurrentExecutionStartTime; + + var token = CurrentCancellationTokenSource; + if (token is not null) + { + try + { + _logger.LogInformation("{Name}: Cancelling", Name); + token.Cancel(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error calling CancellationToken.Cancel();"); + } + } + + var task = _currentTask; + if (task is not null) + { + try + { + _logger.LogInformation("{Name}: Waiting on Task", Name); + var exited = task.Wait(2000); + + if (exited) + { + _logger.LogInformation("{Name}: Task exited", Name); + } + else + { + _logger.LogInformation("{Name}: Timed out waiting for task to stop", Name); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error calling Task.WaitAll();"); + } + } + + if (token is not null) + { + try + { + _logger.LogDebug("{Name}: Disposing CancellationToken", Name); + token.Dispose(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error calling CancellationToken.Dispose();"); + } + } + + if (wasRunning) + { + OnTaskCompleted(startTime, DateTime.UtcNow, TaskCompletionStatus.Aborted, null); + } + } + } + + /// <summary> + /// Converts a TaskTriggerInfo into a concrete BaseTaskTrigger. + /// </summary> + /// <param name="info">The info.</param> + /// <returns>BaseTaskTrigger.</returns> + /// <exception cref="ArgumentException">Invalid trigger type: + info.Type.</exception> + private ITaskTrigger GetTrigger(TaskTriggerInfo info) + { + var options = new TaskOptions + { + MaxRuntimeTicks = info.MaxRuntimeTicks + }; + + if (info.Type == TaskTriggerInfoType.DailyTrigger) + { + if (!info.TimeOfDayTicks.HasValue) + { + throw new ArgumentException("Info did not contain a TimeOfDayTicks.", nameof(info)); + } + + return new DailyTrigger(TimeSpan.FromTicks(info.TimeOfDayTicks.Value), options); + } + + if (info.Type == TaskTriggerInfoType.WeeklyTrigger) + { + if (!info.TimeOfDayTicks.HasValue) + { + throw new ArgumentException("Info did not contain a TimeOfDayTicks.", nameof(info)); + } + + if (!info.DayOfWeek.HasValue) + { + throw new ArgumentException("Info did not contain a DayOfWeek.", nameof(info)); + } + + return new WeeklyTrigger(TimeSpan.FromTicks(info.TimeOfDayTicks.Value), info.DayOfWeek.Value, options); + } + + if (info.Type == TaskTriggerInfoType.IntervalTrigger) + { + if (!info.IntervalTicks.HasValue) + { + throw new ArgumentException("Info did not contain a IntervalTicks.", nameof(info)); + } + + return new IntervalTrigger(TimeSpan.FromTicks(info.IntervalTicks.Value), options); + } + + if (info.Type == TaskTriggerInfoType.StartupTrigger) + { + return new StartupTrigger(options); + } + + throw new ArgumentException("Unrecognized trigger type: " + info.Type); + } + + /// <summary> + /// Disposes each trigger. + /// </summary> + private void DisposeTriggers() + { + foreach (var triggerInfo in InternalTriggers) + { + var trigger = triggerInfo.Item2; + trigger.Triggered -= OnTriggerTriggered; + trigger.Stop(); + if (trigger is IDisposable disposable) + { + disposable.Dispose(); + } + } + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs new file mode 100644 index 00000000..4ec2c9c7 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -0,0 +1,263 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Data.Events; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks; + +/// <summary> +/// Class TaskManager. +/// </summary> +public class TaskManager : ITaskManager +{ + /// <summary> + /// The _task queue. + /// </summary> + private readonly ConcurrentQueue<Tuple<Type, TaskOptions>> _taskQueue = + new ConcurrentQueue<Tuple<Type, TaskOptions>>(); + + private readonly IApplicationPaths _applicationPaths; + private readonly ILogger<TaskManager> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="TaskManager" /> class. + /// </summary> + /// <param name="applicationPaths">The application paths.</param> + /// <param name="logger">The logger.</param> + public TaskManager( + IApplicationPaths applicationPaths, + ILogger<TaskManager> logger) + { + _applicationPaths = applicationPaths; + _logger = logger; + + ScheduledTasks = []; + } + + /// <inheritdoc /> + public event EventHandler<GenericEventArgs<IScheduledTaskWorker>>? TaskExecuting; + + /// <inheritdoc /> + public event EventHandler<TaskCompletionEventArgs>? TaskCompleted; + + /// <inheritdoc /> + public IReadOnlyList<IScheduledTaskWorker> ScheduledTasks { get; private set; } + + /// <inheritdoc /> + public void CancelIfRunningAndQueue<T>(TaskOptions options) + where T : IScheduledTask + { + var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T)); + ((ScheduledTaskWorker)task).CancelIfRunning(); + + QueueScheduledTask<T>(options); + } + + /// <inheritdoc /> + public void CancelIfRunningAndQueue<T>() + where T : IScheduledTask + { + CancelIfRunningAndQueue<T>(new TaskOptions()); + } + + /// <inheritdoc /> + public void CancelIfRunning<T>() + where T : IScheduledTask + { + var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T)); + ((ScheduledTaskWorker)task).CancelIfRunning(); + } + + /// <inheritdoc /> + public void QueueScheduledTask<T>(TaskOptions options) + where T : IScheduledTask + { + var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == typeof(T)); + + if (scheduledTask is null) + { + _logger.LogError("Unable to find scheduled task of type {Type} in QueueScheduledTask.", typeof(T).Name); + } + else + { + QueueScheduledTask(scheduledTask, options); + } + } + + /// <inheritdoc /> + public void QueueScheduledTask<T>() + where T : IScheduledTask + { + QueueScheduledTask<T>(new TaskOptions()); + } + + /// <inheritdoc /> + public void QueueIfNotRunning<T>() + where T : IScheduledTask + { + var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T)); + + if (task.State != TaskState.Running) + { + QueueScheduledTask<T>(new TaskOptions()); + } + } + + /// <inheritdoc /> + public void Execute<T>() + where T : IScheduledTask + { + var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == typeof(T)); + + if (scheduledTask is null) + { + _logger.LogError("Unable to find scheduled task of type {Type} in Execute.", typeof(T).Name); + } + else + { + var type = scheduledTask.ScheduledTask.GetType(); + + _logger.LogDebug("Queuing task {Name}", type.Name); + + lock (_taskQueue) + { + if (scheduledTask.State == TaskState.Idle) + { + Execute(scheduledTask, new TaskOptions()); + } + } + } + } + + /// <inheritdoc /> + public void QueueScheduledTask(IScheduledTask task, TaskOptions options) + { + var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == task.GetType()); + + if (scheduledTask is null) + { + _logger.LogError("Unable to find scheduled task of type {Type} in QueueScheduledTask.", task.GetType().Name); + } + else + { + QueueScheduledTask(scheduledTask, options); + } + } + + /// <summary> + /// Queues the scheduled task. + /// </summary> + /// <param name="task">The task.</param> + /// <param name="options">The task options.</param> + private void QueueScheduledTask(IScheduledTaskWorker task, TaskOptions options) + { + var type = task.ScheduledTask.GetType(); + + _logger.LogDebug("Queuing task {Name}", type.Name); + + lock (_taskQueue) + { + if (task.State == TaskState.Idle) + { + Execute(task, options); + return; + } + + _taskQueue.Enqueue(new Tuple<Type, TaskOptions>(type, options)); + } + } + + /// <inheritdoc /> + public void AddTasks(IEnumerable<IScheduledTask> tasks) + { + var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _logger)); + + ScheduledTasks = ScheduledTasks.Concat(list).ToArray(); + } + + /// <inheritdoc /> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> + /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool dispose) + { + foreach (var task in ScheduledTasks) + { + task.Dispose(); + } + } + + /// <inheritdoc /> + public void Cancel(IScheduledTaskWorker task) + { + ((ScheduledTaskWorker)task).Cancel(); + } + + /// <inheritdoc /> + public Task Execute(IScheduledTaskWorker task, TaskOptions options) + { + return ((ScheduledTaskWorker)task).Execute(options); + } + + /// <summary> + /// Called when [task executing]. + /// </summary> + /// <param name="task">The task.</param> + internal void OnTaskExecuting(IScheduledTaskWorker task) + { + TaskExecuting?.Invoke(this, new GenericEventArgs<IScheduledTaskWorker>(task)); + } + + /// <summary> + /// Called when [task completed]. + /// </summary> + /// <param name="task">The task.</param> + /// <param name="result">The result.</param> + internal void OnTaskCompleted(IScheduledTaskWorker task, TaskResult result) + { + TaskCompleted?.Invoke(task, new TaskCompletionEventArgs(task, result)); + + ExecuteQueuedTasks(); + } + + /// <summary> + /// Executes the queued tasks. + /// </summary> + private void ExecuteQueuedTasks() + { + lock (_taskQueue) + { + var list = new List<Tuple<Type, TaskOptions>>(); + + while (_taskQueue.TryDequeue(out var item)) + { + if (list.All(i => i.Item1 != item.Item1)) + { + list.Add(item); + } + } + + foreach (var enqueuedType in list) + { + var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == enqueuedType.Item1); + + if (scheduledTask.State == TaskState.Idle) + { + Execute(scheduledTask, enqueuedType.Item2); + } + } + } + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs new file mode 100644 index 00000000..36708e25 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs @@ -0,0 +1,295 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks; + +/// <summary> +/// The audio normalization task. +/// </summary> +public partial class AudioNormalizationTask : IScheduledTask +{ + private readonly IItemRepository _itemRepository; + private readonly ILibraryManager _libraryManager; + private readonly IMediaEncoder _mediaEncoder; + private readonly IApplicationPaths _applicationPaths; + private readonly ILocalizationManager _localization; + private readonly ILogger<AudioNormalizationTask> _logger; + + private static readonly TimeSpan _dbSaveInterval = TimeSpan.FromMinutes(5); + + /// <summary> + /// Initializes a new instance of the <see cref="AudioNormalizationTask"/> class. + /// </summary> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> + /// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param> + /// <param name="localizationManager">Instance of the <see cref="ILocalizationManager"/> interface.</param> + /// <param name="logger">Instance of the <see cref="ILogger{AudioNormalizationTask}"/> interface.</param> + public AudioNormalizationTask( + IItemRepository itemRepository, + ILibraryManager libraryManager, + IMediaEncoder mediaEncoder, + IApplicationPaths applicationPaths, + ILocalizationManager localizationManager, + ILogger<AudioNormalizationTask> logger) + { + _itemRepository = itemRepository; + _libraryManager = libraryManager; + _mediaEncoder = mediaEncoder; + _applicationPaths = applicationPaths; + _localization = localizationManager; + _logger = logger; + } + + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskAudioNormalization"); + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("TaskAudioNormalizationDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + + /// <inheritdoc /> + public string Key => "AudioNormalization"; + + [GeneratedRegex(@"^\s+I:\s+(.*?)\s+LUFS")] + private static partial Regex LUFSRegex(); + + /// <inheritdoc /> + public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) + { + var numComplete = 0; + var libraries = _libraryManager.RootFolder.Children.Where(library => _libraryManager.GetLibraryOptions(library).EnableLUFSScan).ToArray(); + double percent = 0; + + foreach (var library in libraries) + { + var startDbSaveInterval = Stopwatch.GetTimestamp(); + var albums = _libraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = [BaseItemKind.MusicAlbum], Parent = library, Recursive = true }); + var toSaveDbItems = new List<BaseItem>(); + + double nextPercent = numComplete + 1; + nextPercent /= libraries.Length; + nextPercent -= percent; + // Split the progress for this single library into two halves: album gain and track gain. + // The first half will be for album gain, the second half for track gain. + nextPercent /= 2; + var albumComplete = 0; + + foreach (var a in albums) + { + if (!a.NormalizationGain.HasValue && !a.LUFS.HasValue) + { + // Album gain + var albumTracks = ((MusicAlbum)a).Tracks.Where(x => x.IsFileProtocol).ToList(); + + // Skip albums that don't have multiple tracks, album gain is useless here + if (albumTracks.Count > 1) + { + _logger.LogInformation("Calculating LUFS for album: {Album} with id: {Id}", a.Name, a.Id); + var tempDir = _applicationPaths.TempDirectory; + Directory.CreateDirectory(tempDir); + var tempFile = Path.Join(tempDir, a.Id + ".concat"); + var inputLines = albumTracks.Select(x => string.Format(CultureInfo.InvariantCulture, "file '{0}'", x.Path.Replace("'", @"'\''", StringComparison.Ordinal))); + await File.WriteAllLinesAsync(tempFile, inputLines, cancellationToken).ConfigureAwait(false); + try + { + a.LUFS = await CalculateLUFSAsync( + string.Format(CultureInfo.InvariantCulture, "-f concat -safe 0 -i \"{0}\"", tempFile), + OperatingSystem.IsWindows(), // Wait for process to exit on Windows before we try deleting the concat file + cancellationToken).ConfigureAwait(false); + toSaveDbItems.Add(a); + } + finally + { + try + { + File.Delete(tempFile); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to delete concat file: {FileName}.", tempFile); + } + } + } + } + + if (Stopwatch.GetElapsedTime(startDbSaveInterval) > _dbSaveInterval) + { + if (toSaveDbItems.Count > 1) + { + _itemRepository.SaveItems(toSaveDbItems, cancellationToken); + toSaveDbItems.Clear(); + } + + startDbSaveInterval = Stopwatch.GetTimestamp(); + } + + // Update sub-progress for album gain + albumComplete++; + double albumPercent = albumComplete; + albumPercent /= albums.Count; + + progress.Report(100 * (percent + (albumPercent * nextPercent))); + } + + // Update progress to start at the track gain percent calculation + percent += nextPercent; + + if (toSaveDbItems.Count > 1) + { + _itemRepository.SaveItems(toSaveDbItems, cancellationToken); + toSaveDbItems.Clear(); + } + + startDbSaveInterval = Stopwatch.GetTimestamp(); + + // Track gain + var tracks = _libraryManager.GetItemList(new InternalItemsQuery { MediaTypes = [MediaType.Audio], IncludeItemTypes = [BaseItemKind.Audio], Parent = library, Recursive = true }); + + var tracksComplete = 0; + foreach (var t in tracks) + { + if (!t.NormalizationGain.HasValue && !t.LUFS.HasValue && t.IsFileProtocol) + { + t.LUFS = await CalculateLUFSAsync( + string.Format(CultureInfo.InvariantCulture, "-i \"{0}\"", t.Path.Replace("\"", "\\\"", StringComparison.Ordinal)), + false, + cancellationToken).ConfigureAwait(false); + toSaveDbItems.Add(t); + } + + if (Stopwatch.GetElapsedTime(startDbSaveInterval) > _dbSaveInterval) + { + if (toSaveDbItems.Count > 1) + { + _itemRepository.SaveItems(toSaveDbItems, cancellationToken); + toSaveDbItems.Clear(); + } + + startDbSaveInterval = Stopwatch.GetTimestamp(); + } + + // Update sub-progress for track gain + tracksComplete++; + double trackPercent = tracksComplete; + trackPercent /= tracks.Count; + + progress.Report(100 * (percent + (trackPercent * nextPercent))); + } + + if (toSaveDbItems.Count > 1) + { + _itemRepository.SaveItems(toSaveDbItems, cancellationToken); + } + + // Update progress + numComplete++; + percent = numComplete; + percent /= libraries.Length; + + progress.Report(100 * percent); + } + + progress.Report(100.0); + } + + /// <inheritdoc /> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + yield return new TaskTriggerInfo + { + Type = TaskTriggerInfoType.IntervalTrigger, + IntervalTicks = TimeSpan.FromHours(24).Ticks + }; + } + + private async Task<float?> CalculateLUFSAsync(string inputArgs, bool waitForExit, CancellationToken cancellationToken) + { + var args = $"-hide_banner {inputArgs} -af ebur128=framelog=verbose -f null -"; + + using (var process = new Process() + { + StartInfo = new ProcessStartInfo + { + FileName = _mediaEncoder.EncoderPath, + Arguments = args, + RedirectStandardOutput = false, + RedirectStandardError = true + }, + }) + { + _logger.LogDebug("Starting ffmpeg with arguments: {Arguments}", args); + try + { + process.Start(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error starting ffmpeg with arguments: {Arguments}", args); + return null; + } + + try + { + process.PriorityClass = ProcessPriorityClass.BelowNormal; + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Error setting ffmpeg process priority"); + } + + using var reader = process.StandardError; + float? lufs = null; + var foundLufs = false; + await foreach (var line in reader.ReadAllLinesAsync(cancellationToken).ConfigureAwait(false)) + { + if (foundLufs) + { + continue; + } + + Match match = LUFSRegex().Match(line); + if (!match.Success) + { + continue; + } + + lufs = float.Parse(match.Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat); + foundLufs = true; + } + + if (lufs is null) + { + _logger.LogError("Failed to find LUFS value in output"); + } + + if (waitForExit) + { + await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false); + } + + return lufs; + } + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs new file mode 100644 index 00000000..f8130956 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Chapters; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks; + +/// <summary> +/// Class ChapterImagesTask. +/// </summary> +public class ChapterImagesTask : IScheduledTask +{ + private readonly ILogger<ChapterImagesTask> _logger; + private readonly ILibraryManager _libraryManager; + private readonly IApplicationPaths _appPaths; + private readonly IChapterManager _chapterManager; + private readonly IFileSystem _fileSystem; + private readonly ILocalizationManager _localization; + + /// <summary> + /// Initializes a new instance of the <see cref="ChapterImagesTask" /> class. + /// </summary> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param> + /// <param name="chapterManager">Instance of the <see cref="IChapterManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> + public ChapterImagesTask( + ILogger<ChapterImagesTask> logger, + ILibraryManager libraryManager, + IApplicationPaths appPaths, + IChapterManager chapterManager, + IFileSystem fileSystem, + ILocalizationManager localization) + { + _logger = logger; + _libraryManager = libraryManager; + _appPaths = appPaths; + _chapterManager = chapterManager; + _fileSystem = fileSystem; + _localization = localization; + } + + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages"); + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + + /// <inheritdoc /> + public string Key => "RefreshChapterImages"; + + /// <inheritdoc /> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + yield return new TaskTriggerInfo + { + Type = TaskTriggerInfoType.DailyTrigger, + TimeOfDayTicks = TimeSpan.FromHours(2).Ticks, + MaxRuntimeTicks = TimeSpan.FromHours(4).Ticks + }; + } + + /// <inheritdoc /> + public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) + { + var videos = _libraryManager.GetItemList(new InternalItemsQuery + { + MediaTypes = [MediaType.Video], + IsFolder = false, + Recursive = true, + DtoOptions = new DtoOptions(false) + { + EnableImages = false + }, + SourceTypes = [SourceType.Library], + IsVirtualItem = false + }) + .OfType<Video>() + .ToList(); + + var numComplete = 0; + + var failHistoryPath = Path.Combine(_appPaths.CachePath, "chapter-failures.txt"); + + List<string> previouslyFailedImages; + + if (File.Exists(failHistoryPath)) + { + try + { + previouslyFailedImages = (await File.ReadAllTextAsync(failHistoryPath, cancellationToken).ConfigureAwait(false)) + .Split('|', StringSplitOptions.RemoveEmptyEntries) + .ToList(); + } + catch (IOException) + { + previouslyFailedImages = []; + } + } + else + { + previouslyFailedImages = []; + } + + var directoryService = new DirectoryService(_fileSystem); + + foreach (var video in videos) + { + cancellationToken.ThrowIfCancellationRequested(); + + var key = video.Path + video.DateModified.Ticks; + + var extract = !previouslyFailedImages.Contains(key, StringComparison.OrdinalIgnoreCase); + + try + { + var chapters = _chapterManager.GetChapters(video.Id); + + var success = await _chapterManager.RefreshChapterImages(video, directoryService, chapters, extract, true, cancellationToken).ConfigureAwait(false); + + if (!success) + { + previouslyFailedImages.Add(key); + + var parentPath = Path.GetDirectoryName(failHistoryPath); + if (parentPath is not null) + { + Directory.CreateDirectory(parentPath); + } + + string text = string.Join('|', previouslyFailedImages); + await File.WriteAllTextAsync(failHistoryPath, text, cancellationToken).ConfigureAwait(false); + } + + numComplete++; + double percent = numComplete; + percent /= videos.Count; + + progress.Report(100 * percent); + } + catch (ObjectDisposedException ex) + { + // TODO Investigate and properly fix. + _logger.LogError(ex, "Object Disposed"); + break; + } + } + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs new file mode 100644 index 00000000..1621bbaa --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks; + +/// <summary> +/// Deletes old activity log entries. +/// </summary> +public class CleanActivityLogTask : IScheduledTask, IConfigurableScheduledTask +{ + private readonly ILocalizationManager _localization; + private readonly IActivityManager _activityManager; + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="CleanActivityLogTask"/> class. + /// </summary> + /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> + /// <param name="activityManager">Instance of the <see cref="IActivityManager"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + public CleanActivityLogTask( + ILocalizationManager localization, + IActivityManager activityManager, + IServerConfigurationManager serverConfigurationManager) + { + _localization = localization; + _activityManager = activityManager; + _serverConfigurationManager = serverConfigurationManager; + } + + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskCleanActivityLog"); + + /// <inheritdoc /> + public string Key => "CleanActivityLog"; + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("TaskCleanActivityLogDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + + /// <inheritdoc /> + public bool IsHidden => false; + + /// <inheritdoc /> + public bool IsEnabled => true; + + /// <inheritdoc /> + public bool IsLogged => true; + + /// <inheritdoc /> + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) + { + var retentionDays = _serverConfigurationManager.Configuration.ActivityLogRetentionDays; + if (!retentionDays.HasValue || retentionDays < 0) + { + throw new InvalidOperationException($"Activity Log Retention days must be at least 0. Currently: {retentionDays}"); + } + + var startDate = DateTime.UtcNow.AddDays(-retentionDays.Value); + return _activityManager.CleanAsync(startDate); + } + + /// <inheritdoc /> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + return []; + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionAndPlaylistPathsTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionAndPlaylistPathsTask.cs new file mode 100644 index 00000000..7f68f770 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionAndPlaylistPathsTask.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Collections; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks; + +/// <summary> +/// Deletes path references from collections and playlists that no longer exists. +/// </summary> +public class CleanupCollectionAndPlaylistPathsTask : IScheduledTask +{ + private readonly ILocalizationManager _localization; + private readonly ICollectionManager _collectionManager; + private readonly IPlaylistManager _playlistManager; + private readonly ILogger<CleanupCollectionAndPlaylistPathsTask> _logger; + private readonly IProviderManager _providerManager; + + /// <summary> + /// Initializes a new instance of the <see cref="CleanupCollectionAndPlaylistPathsTask"/> class. + /// </summary> + /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> + /// <param name="collectionManager">Instance of the <see cref="ICollectionManager"/> interface.</param> + /// <param name="playlistManager">Instance of the <see cref="IPlaylistManager"/> interface.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + public CleanupCollectionAndPlaylistPathsTask( + ILocalizationManager localization, + ICollectionManager collectionManager, + IPlaylistManager playlistManager, + ILogger<CleanupCollectionAndPlaylistPathsTask> logger, + IProviderManager providerManager) + { + _localization = localization; + _collectionManager = collectionManager; + _playlistManager = playlistManager; + _logger = logger; + _providerManager = providerManager; + } + + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskCleanCollectionsAndPlaylists"); + + /// <inheritdoc /> + public string Key => "CleanCollectionsAndPlaylists"; + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("TaskCleanCollectionsAndPlaylistsDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + + /// <inheritdoc /> + public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) + { + var collectionsFolder = await _collectionManager.GetCollectionsFolder(false).ConfigureAwait(false); + if (collectionsFolder is null) + { + _logger.LogDebug("There is no collections folder to be found"); + } + else + { + var collections = collectionsFolder.Children.OfType<BoxSet>().ToArray(); + _logger.LogDebug("Found {CollectionLength} boxsets", collections.Length); + + for (var index = 0; index < collections.Length; index++) + { + var collection = collections[index]; + _logger.LogDebug("Checking boxset {CollectionName}", collection.Name); + + await CleanupLinkedChildrenAsync(collection, cancellationToken).ConfigureAwait(false); + progress.Report(50D / collections.Length * (index + 1)); + } + } + + var playlistsFolder = _playlistManager.GetPlaylistsFolder(); + if (playlistsFolder is null) + { + _logger.LogDebug("There is no playlists folder to be found"); + return; + } + + var playlists = playlistsFolder.Children.OfType<Playlist>().ToArray(); + _logger.LogDebug("Found {PlaylistLength} playlists", playlists.Length); + + for (var index = 0; index < playlists.Length; index++) + { + var playlist = playlists[index]; + _logger.LogDebug("Checking playlist {PlaylistName}", playlist.Name); + + await CleanupLinkedChildrenAsync(playlist, cancellationToken).ConfigureAwait(false); + progress.Report(50D / playlists.Length * (index + 1)); + } + } + + private async Task CleanupLinkedChildrenAsync<T>(T folder, CancellationToken cancellationToken) + where T : Folder + { + List<LinkedChild>? itemsToRemove = null; + foreach (var linkedChild in folder.LinkedChildren) + { + var path = linkedChild.Path; + if (!File.Exists(path) && !Directory.Exists(path)) + { + _logger.LogInformation("Item in {FolderName} cannot be found at {ItemPath}", folder.Name, path); + (itemsToRemove ??= new List<LinkedChild>()).Add(linkedChild); + } + } + + if (itemsToRemove is not null) + { + _logger.LogDebug("Updating {FolderName}", folder.Name); + folder.LinkedChildren = folder.LinkedChildren.Except(itemsToRemove).ToArray(); + await _providerManager.SaveMetadataAsync(folder, ItemUpdateType.MetadataEdit).ConfigureAwait(false); + await folder.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false); + } + } + + /// <inheritdoc /> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + yield return new TaskTriggerInfo + { + Type = TaskTriggerInfoType.StartupTrigger, + }; + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs new file mode 100644 index 00000000..4156050e --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupUserDataTask.cs @@ -0,0 +1,77 @@ +#pragma warning disable RS0030 // Do not use banned APIs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations; +using Jellyfin.Server.Implementations.Item; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks; + +/// <summary> +/// Task to clean up any detached userdata from the database. +/// </summary> +public class CleanupUserDataTask : IScheduledTask +{ + private readonly ILocalizationManager _localization; + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + private readonly ILogger<CleanupUserDataTask> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="CleanupUserDataTask"/> class. + /// </summary> + /// <param name="localization">The localisation Provider.</param> + /// <param name="dbProvider">The DB context factory.</param> + /// <param name="logger">A logger.</param> + public CleanupUserDataTask(ILocalizationManager localization, IDbContextFactory<JellyfinDbContext> dbProvider, ILogger<CleanupUserDataTask> logger) + { + _localization = localization; + _dbProvider = dbProvider; + _logger = logger; + } + + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("CleanupUserDataTask"); + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("CleanupUserDataTaskDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + + /// <inheritdoc /> + public string Key => nameof(CleanupUserDataTask); + + /// <inheritdoc/> + public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) + { + const int LimitDays = 90; + var userDataDate = DateTime.UtcNow.AddDays(LimitDays * -1); + var dbContext = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + var detachedUserData = dbContext.UserData.Where(e => e.ItemId == BaseItemRepository.PlaceholderId); + _logger.LogInformation("There are {NoDetached} detached UserData entries.", detachedUserData.Count()); + + detachedUserData = detachedUserData.Where(e => e.RetentionDate < userDataDate); + + _logger.LogInformation("{NoDetached} are older then {Limit} days.", detachedUserData.Count(), LimitDays); + + await detachedUserData.ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false); + } + + progress.Report(100); + } + + /// <inheritdoc/> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + yield break; + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs new file mode 100644 index 00000000..0e77f010 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.IO; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks; + +/// <summary> +/// Deletes old cache files. +/// </summary> +public class DeleteCacheFileTask : IScheduledTask, IConfigurableScheduledTask +{ + /// <summary> + /// Gets or sets the application paths. + /// </summary> + /// <value>The application paths.</value> + private readonly IApplicationPaths _applicationPaths; + private readonly ILogger<DeleteCacheFileTask> _logger; + private readonly IFileSystem _fileSystem; + private readonly ILocalizationManager _localization; + + /// <summary> + /// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class. + /// </summary> + /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> + public DeleteCacheFileTask( + IApplicationPaths appPaths, + ILogger<DeleteCacheFileTask> logger, + IFileSystem fileSystem, + ILocalizationManager localization) + { + _applicationPaths = appPaths; + _logger = logger; + _fileSystem = fileSystem; + _localization = localization; + } + + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskCleanCache"); + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("TaskCleanCacheDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + + /// <inheritdoc /> + public string Key => "DeleteCacheFiles"; + + /// <inheritdoc /> + public bool IsHidden => false; + + /// <inheritdoc /> + public bool IsEnabled => true; + + /// <inheritdoc /> + public bool IsLogged => true; + + /// <inheritdoc /> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + yield return new TaskTriggerInfo + { + Type = TaskTriggerInfoType.IntervalTrigger, + IntervalTicks = TimeSpan.FromHours(24).Ticks + }; + } + + /// <inheritdoc /> + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) + { + var minDateModified = DateTime.UtcNow.AddDays(-30); + + try + { + DeleteCacheFilesFromDirectory(_applicationPaths.CachePath, minDateModified, progress, cancellationToken); + } + catch (DirectoryNotFoundException) + { + // No biggie here. Nothing to delete + } + + progress.Report(90); + + minDateModified = DateTime.UtcNow.AddDays(-1); + + try + { + DeleteCacheFilesFromDirectory(_applicationPaths.TempDirectory, minDateModified, progress, cancellationToken); + } + catch (DirectoryNotFoundException) + { + // No biggie here. Nothing to delete + } + + return Task.CompletedTask; + } + + /// <summary> + /// Deletes the cache files from directory with a last write time less than a given date. + /// </summary> + /// <param name="directory">The directory.</param> + /// <param name="minDateModified">The min date modified.</param> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The task cancellation token.</param> + private void DeleteCacheFilesFromDirectory(string directory, DateTime minDateModified, IProgress<double> progress, CancellationToken cancellationToken) + { + var filesToDelete = _fileSystem.GetFiles(directory, true) + .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified) + .ToList(); + + var index = 0; + + foreach (var file in filesToDelete) + { + double percent = index; + percent /= filesToDelete.Count; + + progress.Report(100 * percent); + + cancellationToken.ThrowIfCancellationRequested(); + + FileSystemHelper.DeleteFile(_fileSystem, file.FullName, _logger); + + index++; + } + + FileSystemHelper.DeleteEmptyFolders(_fileSystem, directory, _logger); + + progress.Report(100); + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs new file mode 100644 index 00000000..69952952 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Tasks; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks; + +/// <summary> +/// Deletes old log files. +/// </summary> +public class DeleteLogFileTask : IScheduledTask, IConfigurableScheduledTask +{ + private readonly IConfigurationManager _configurationManager; + private readonly IFileSystem _fileSystem; + private readonly ILocalizationManager _localization; + + /// <summary> + /// Initializes a new instance of the <see cref="DeleteLogFileTask" /> class. + /// </summary> + /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> + public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization) + { + _configurationManager = configurationManager; + _fileSystem = fileSystem; + _localization = localization; + } + + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskCleanLogs"); + + /// <inheritdoc /> + public string Description => string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("TaskCleanLogsDescription"), + _configurationManager.CommonConfiguration.LogFileRetentionDays); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + + /// <inheritdoc /> + public string Key => "CleanLogFiles"; + + /// <inheritdoc /> + public bool IsHidden => false; + + /// <inheritdoc /> + public bool IsEnabled => true; + + /// <inheritdoc /> + public bool IsLogged => true; + + /// <inheritdoc /> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + yield return new TaskTriggerInfo + { + Type = TaskTriggerInfoType.IntervalTrigger, + IntervalTicks = TimeSpan.FromHours(24).Ticks + }; + } + + /// <inheritdoc /> + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) + { + // Delete log files more than n days old + var minDateModified = DateTime.UtcNow.AddDays(-_configurationManager.CommonConfiguration.LogFileRetentionDays); + + // Only delete files that serilog doesn't manage (anything that doesn't start with 'log_' + var filesToDelete = _fileSystem.GetFiles(_configurationManager.CommonApplicationPaths.LogDirectoryPath, true) + .Where(f => !f.Name.StartsWith("log_", StringComparison.Ordinal) + && _fileSystem.GetLastWriteTimeUtc(f) < minDateModified) + .ToList(); + + var index = 0; + + foreach (var file in filesToDelete) + { + double percent = index / (double)filesToDelete.Count; + + progress.Report(100 * percent); + + cancellationToken.ThrowIfCancellationRequested(); + + _fileSystem.DeleteFile(file.FullName); + + index++; + } + + progress.Report(100); + + return Task.CompletedTask; + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs new file mode 100644 index 00000000..9cc2cc51 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.IO; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks; + +/// <summary> +/// Deletes all transcoding temp files. +/// </summary> +public class DeleteTranscodeFileTask : IScheduledTask, IConfigurableScheduledTask +{ + private readonly ILogger<DeleteTranscodeFileTask> _logger; + private readonly IConfigurationManager _configurationManager; + private readonly IFileSystem _fileSystem; + private readonly ILocalizationManager _localization; + + /// <summary> + /// Initializes a new instance of the <see cref="DeleteTranscodeFileTask"/> class. + /// </summary> + /// <param name="logger">Instance of the <see cref="ILogger{DeleteTranscodeFileTask}"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param> + /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> + public DeleteTranscodeFileTask( + ILogger<DeleteTranscodeFileTask> logger, + IFileSystem fileSystem, + IConfigurationManager configurationManager, + ILocalizationManager localization) + { + _logger = logger; + _fileSystem = fileSystem; + _configurationManager = configurationManager; + _localization = localization; + } + + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskCleanTranscode"); + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("TaskCleanTranscodeDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + + /// <inheritdoc /> + public string Key => "DeleteTranscodeFiles"; + + /// <inheritdoc /> + public bool IsHidden => false; + + /// <inheritdoc /> + public bool IsEnabled => true; + + /// <inheritdoc /> + public bool IsLogged => true; + + /// <inheritdoc /> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + yield return new TaskTriggerInfo + { + Type = TaskTriggerInfoType.StartupTrigger + }; + + yield return new TaskTriggerInfo + { + Type = TaskTriggerInfoType.IntervalTrigger, + IntervalTicks = TimeSpan.FromHours(24).Ticks + }; + } + + /// <inheritdoc /> + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) + { + var minDateModified = DateTime.UtcNow.AddDays(-1); + progress.Report(50); + + DeleteTempFilesFromDirectory(_configurationManager.GetTranscodePath(), minDateModified, progress, cancellationToken); + + return Task.CompletedTask; + } + + /// <summary> + /// Deletes the transcoded temp files from directory with a last write time less than a given date. + /// </summary> + /// <param name="directory">The directory.</param> + /// <param name="minDateModified">The min date modified.</param> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The task cancellation token.</param> + private void DeleteTempFilesFromDirectory(string directory, DateTime minDateModified, IProgress<double> progress, CancellationToken cancellationToken) + { + var filesToDelete = _fileSystem.GetFiles(directory, true) + .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified) + .ToList(); + + var index = 0; + + foreach (var file in filesToDelete) + { + double percent = index; + percent /= filesToDelete.Count; + + progress.Report(100 * percent); + + cancellationToken.ThrowIfCancellationRequested(); + + FileSystemHelper.DeleteFile(_fileSystem, file.FullName, _logger); + + index++; + } + + FileSystemHelper.DeleteEmptyFolders(_fileSystem, directory, _logger); + + progress.Report(100); + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/MediaSegmentExtractionTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/MediaSegmentExtractionTask.cs new file mode 100644 index 00000000..51920c5b --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/MediaSegmentExtractionTask.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks; + +/// <summary> +/// Task to obtain media segments. +/// </summary> +public class MediaSegmentExtractionTask : IScheduledTask +{ + /// <summary> + /// The library manager. + /// </summary> + private readonly ILibraryManager _libraryManager; + private readonly ILocalizationManager _localization; + private readonly IMediaSegmentManager _mediaSegmentManager; + private static readonly BaseItemKind[] _itemTypes = [BaseItemKind.Episode, BaseItemKind.Movie, BaseItemKind.Audio, BaseItemKind.AudioBook]; + + /// <summary> + /// Initializes a new instance of the <see cref="MediaSegmentExtractionTask" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="localization">The localization manager.</param> + /// <param name="mediaSegmentManager">The segment manager.</param> + public MediaSegmentExtractionTask(ILibraryManager libraryManager, ILocalizationManager localization, IMediaSegmentManager mediaSegmentManager) + { + _libraryManager = libraryManager; + _localization = localization; + _mediaSegmentManager = mediaSegmentManager; + } + + /// <inheritdoc/> + public string Name => _localization.GetLocalizedString("TaskExtractMediaSegments"); + + /// <inheritdoc/> + public string Description => _localization.GetLocalizedString("TaskExtractMediaSegmentsDescription"); + + /// <inheritdoc/> + public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + + /// <inheritdoc/> + public string Key => "TaskExtractMediaSegments"; + + /// <inheritdoc/> + public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + progress.Report(0); + + var pagesize = 100; + + var query = new InternalItemsQuery + { + MediaTypes = [MediaType.Video, MediaType.Audio], + IsVirtualItem = false, + IncludeItemTypes = _itemTypes, + DtoOptions = new DtoOptions(true), + SourceTypes = [SourceType.Library], + Recursive = true, + Limit = pagesize + }; + + var numberOfVideos = _libraryManager.GetCount(query); + + var startIndex = 0; + var numComplete = 0; + + while (startIndex < numberOfVideos) + { + query.StartIndex = startIndex; + + var baseItems = _libraryManager.GetItemList(query); + var currentPageCount = baseItems.Count; + // TODO parallelize with Parallel.ForEach? + for (var i = 0; i < currentPageCount; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + var item = baseItems[i]; + // Only local files supported + if (item.IsFileProtocol && File.Exists(item.Path)) + { + var libraryOptions = _libraryManager.GetLibraryOptions(item); + await _mediaSegmentManager.RunSegmentPluginProviders(item, libraryOptions, false, cancellationToken).ConfigureAwait(false); + } + + // Update progress + numComplete++; + double percent = (double)numComplete / numberOfVideos; + progress.Report(100 * percent); + } + + startIndex += pagesize; + } + + progress.Report(100); + } + + /// <inheritdoc/> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + yield return new TaskTriggerInfo + { + Type = TaskTriggerInfoType.IntervalTrigger, + IntervalTicks = TimeSpan.FromHours(12).Ticks + }; + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs new file mode 100644 index 00000000..92d7a390 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks; + +/// <summary> +/// Optimizes Jellyfin's database by issuing a VACUUM command. +/// </summary> +public class OptimizeDatabaseTask : IScheduledTask, IConfigurableScheduledTask +{ + private readonly ILogger<OptimizeDatabaseTask> _logger; + private readonly ILocalizationManager _localization; + private readonly IJellyfinDatabaseProvider _jellyfinDatabaseProvider; + + /// <summary> + /// Initializes a new instance of the <see cref="OptimizeDatabaseTask" /> class. + /// </summary> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> + /// <param name="jellyfinDatabaseProvider">Instance of the JellyfinDatabaseProvider that can be used for provider specific operations.</param> + public OptimizeDatabaseTask( + ILogger<OptimizeDatabaseTask> logger, + ILocalizationManager localization, + IJellyfinDatabaseProvider jellyfinDatabaseProvider) + { + _logger = logger; + _localization = localization; + _jellyfinDatabaseProvider = jellyfinDatabaseProvider; + } + + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskOptimizeDatabase"); + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("TaskOptimizeDatabaseDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + + /// <inheritdoc /> + public string Key => "OptimizeDatabaseTask"; + + /// <inheritdoc /> + public bool IsHidden => false; + + /// <inheritdoc /> + public bool IsEnabled => true; + + /// <inheritdoc /> + public bool IsLogged => true; + + /// <inheritdoc /> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + yield return new TaskTriggerInfo + { + Type = TaskTriggerInfoType.IntervalTrigger, + IntervalTicks = TimeSpan.FromHours(6).Ticks + }; + } + + /// <inheritdoc /> + public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) + { + _logger.LogInformation("Optimizing and vacuuming jellyfin.db..."); + + try + { + await _jellyfinDatabaseProvider.RunScheduledOptimisation(cancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + _logger.LogError(e, "Error while optimizing jellyfin.db"); + } + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs new file mode 100644 index 00000000..6e4e5c78 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs @@ -0,0 +1,129 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks; + +/// <summary> +/// Class PeopleValidationTask. +/// </summary> +public class PeopleValidationTask : IScheduledTask, IConfigurableScheduledTask +{ + private readonly ILibraryManager _libraryManager; + private readonly ILocalizationManager _localization; + private readonly IDbContextFactory<JellyfinDbContext> _dbContextFactory; + + /// <summary> + /// Initializes a new instance of the <see cref="PeopleValidationTask" /> class. + /// </summary> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> + /// <param name="dbContextFactory">Instance of the <see cref="IDbContextFactory{TContext}"/> interface.</param> + public PeopleValidationTask(ILibraryManager libraryManager, ILocalizationManager localization, IDbContextFactory<JellyfinDbContext> dbContextFactory) + { + _libraryManager = libraryManager; + _localization = localization; + _dbContextFactory = dbContextFactory; + } + + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskRefreshPeople"); + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("TaskRefreshPeopleDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + + /// <inheritdoc /> + public string Key => "RefreshPeople"; + + /// <inheritdoc /> + public bool IsHidden => false; + + /// <inheritdoc /> + public bool IsEnabled => true; + + /// <inheritdoc /> + public bool IsLogged => true; + + /// <summary> + /// Creates the triggers that define when the task will run. + /// </summary> + /// <returns>An <see cref="IEnumerable{TaskTriggerInfo}"/> containing the default trigger infos for this task.</returns> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + yield return new TaskTriggerInfo + { + Type = TaskTriggerInfoType.IntervalTrigger, + IntervalTicks = TimeSpan.FromDays(7).Ticks + }; + } + + /// <inheritdoc /> + public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) + { + IProgress<double> subProgress = new Progress<double>((val) => progress.Report(val / 2)); + await _libraryManager.ValidatePeopleAsync(subProgress, cancellationToken).ConfigureAwait(false); + + subProgress = new Progress<double>((val) => progress.Report((val / 2) + 50)); + var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + await using (context.ConfigureAwait(false)) + { + var dupQuery = context.Peoples + .GroupBy(e => new { e.Name, e.PersonType }) + .Where(e => e.Count() > 1) + .Select(e => e.Select(f => f.Id).ToArray()); + + var total = dupQuery.Count(); + + const int PartitionSize = 100; + var iterator = 0; + int itemCounter; + var buffer = ArrayPool<Guid[]>.Shared.Rent(PartitionSize)!; + try + { + do + { + itemCounter = 0; + await foreach (var item in dupQuery + .Take(PartitionSize) + .AsAsyncEnumerable() + .WithCancellation(cancellationToken) + .ConfigureAwait(false)) + { + buffer[itemCounter++] = item; + } + + for (int i = 0; i < itemCounter; i++) + { + var item = buffer[i]; + var reference = item[0]; + var dups = item[1..]; + await context.PeopleBaseItemMap.WhereOneOrMany(dups, e => e.PeopleId) + .ExecuteUpdateAsync(e => e.SetProperty(f => f.PeopleId, reference), cancellationToken) + .ConfigureAwait(false); + await context.Peoples.Where(e => dups.Contains(e.Id)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false); + subProgress.Report(100f / total * ((iterator * PartitionSize) + i)); + } + + iterator++; + } while (itemCounter == PartitionSize && !cancellationToken.IsCancellationRequested); + } + finally + { + ArrayPool<Guid[]>.Shared.Return(buffer); + } + + subProgress.Report(100); + } + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs new file mode 100644 index 00000000..31153af2 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Updates; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks; + +/// <summary> +/// Plugin Update Task. +/// </summary> +public class PluginUpdateTask : IScheduledTask, IConfigurableScheduledTask +{ + private readonly ILogger<PluginUpdateTask> _logger; + + private readonly IInstallationManager _installationManager; + private readonly ILocalizationManager _localization; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginUpdateTask" /> class. + /// </summary> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="installationManager">Instance of the <see cref="IInstallationManager"/> interface.</param> + /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> + public PluginUpdateTask(ILogger<PluginUpdateTask> logger, IInstallationManager installationManager, ILocalizationManager localization) + { + _logger = logger; + _installationManager = installationManager; + _localization = localization; + } + + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskUpdatePlugins"); + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("TaskUpdatePluginsDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksApplicationCategory"); + + /// <inheritdoc /> + public string Key => "PluginUpdates"; + + /// <inheritdoc /> + public bool IsHidden => false; + + /// <inheritdoc /> + public bool IsEnabled => true; + + /// <inheritdoc /> + public bool IsLogged => true; + + /// <inheritdoc /> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + yield return new TaskTriggerInfo + { + Type = TaskTriggerInfoType.StartupTrigger + }; + + yield return new TaskTriggerInfo + { + Type = TaskTriggerInfoType.IntervalTrigger, + IntervalTicks = TimeSpan.FromHours(24).Ticks + }; + } + + /// <inheritdoc /> + public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) + { + progress.Report(0); + + var packageFetchTask = _installationManager.GetAvailablePluginUpdates(cancellationToken); + var packagesToInstall = (await packageFetchTask.ConfigureAwait(false)).ToList(); + + progress.Report(10); + + var numComplete = 0; + + foreach (var package in packagesToInstall) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + await _installationManager.InstallPackage(package, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // InstallPackage has its own inner cancellation token, so only throw this if it's ours + if (cancellationToken.IsCancellationRequested) + { + throw; + } + } + catch (HttpRequestException ex) + { + _logger.LogError(ex, "Error downloading {Name}", package.Name); + } + catch (IOException ex) + { + _logger.LogError(ex, "Error updating {Name}", package.Name); + } + catch (InvalidDataException ex) + { + _logger.LogError(ex, "Error updating {Name}", package.Name); + } + + // Update progress + lock (progress) + { + progress.Report((90.0 * ++numComplete / packagesToInstall.Count) + 10); + } + } + + progress.Report(100); + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs new file mode 100644 index 00000000..1865189d --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Implementations.Library; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks; + +/// <summary> +/// Class RefreshMediaLibraryTask. +/// </summary> +public class RefreshMediaLibraryTask : IScheduledTask +{ + /// <summary> + /// The _library manager. + /// </summary> + private readonly ILibraryManager _libraryManager; + private readonly ILocalizationManager _localization; + + /// <summary> + /// Initializes a new instance of the <see cref="RefreshMediaLibraryTask" /> class. + /// </summary> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> + public RefreshMediaLibraryTask(ILibraryManager libraryManager, ILocalizationManager localization) + { + _libraryManager = libraryManager; + _localization = localization; + } + + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskRefreshLibrary"); + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("TaskRefreshLibraryDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + + /// <inheritdoc /> + public string Key => "RefreshLibrary"; + + /// <inheritdoc /> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + yield return new TaskTriggerInfo + { + Type = TaskTriggerInfoType.IntervalTrigger, + IntervalTicks = TimeSpan.FromHours(12).Ticks + }; + } + + /// <inheritdoc /> + public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + progress.Report(0); + + await ((LibraryManager)_libraryManager).ValidateMediaLibraryInternal(progress, cancellationToken).ConfigureAwait(false); + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs new file mode 100644 index 00000000..9abcd9c7 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -0,0 +1,86 @@ +using System; +using System.Threading; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks.Triggers; + +/// <summary> +/// Represents a task trigger that fires everyday. +/// </summary> +public sealed class DailyTrigger : ITaskTrigger, IDisposable +{ + private readonly TimeSpan _timeOfDay; + private Timer? _timer; + private bool _disposed; + + /// <summary> + /// Initializes a new instance of the <see cref="DailyTrigger"/> class. + /// </summary> + /// <param name="timeOfDay">The time of day to trigger the task to run.</param> + /// <param name="taskOptions">The options of this task.</param> + public DailyTrigger(TimeSpan timeOfDay, TaskOptions taskOptions) + { + _timeOfDay = timeOfDay; + TaskOptions = taskOptions; + } + + /// <inheritdoc /> + public event EventHandler<EventArgs>? Triggered; + + /// <inheritdoc /> + public TaskOptions TaskOptions { get; } + + /// <inheritdoc /> + public void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup) + { + DisposeTimer(); + + var now = DateTime.Now; + + var triggerDate = now.TimeOfDay > _timeOfDay ? now.Date.AddDays(1) : now.Date; + triggerDate = triggerDate.Add(_timeOfDay); + + var dueTime = triggerDate - now; + + logger.LogInformation("Daily trigger for {Task} set to fire at {TriggerDate:yyyy-MM-dd HH:mm:ss.fff zzz}, which is {DueTime:c} from now.", taskName, triggerDate, dueTime); + + _timer = new Timer(_ => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1)); + } + + /// <inheritdoc /> + public void Stop() + { + DisposeTimer(); + } + + /// <summary> + /// Disposes the timer. + /// </summary> + private void DisposeTimer() + { + _timer?.Dispose(); + _timer = null; + } + + /// <summary> + /// Called when [triggered]. + /// </summary> + private void OnTriggered() + { + Triggered?.Invoke(this, EventArgs.Empty); + } + + /// <inheritdoc /> + public void Dispose() + { + if (_disposed) + { + return; + } + + DisposeTimer(); + + _disposed = true; + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs new file mode 100644 index 00000000..d6773b65 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs @@ -0,0 +1,106 @@ +using System; +using System.Linq; +using System.Threading; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks.Triggers; + +/// <summary> +/// Represents a task trigger that runs repeatedly on an interval. +/// </summary> +public sealed class IntervalTrigger : ITaskTrigger, IDisposable +{ + private readonly TimeSpan _interval; + private DateTime _lastStartDate; + private Timer? _timer; + private bool _disposed; + + /// <summary> + /// Initializes a new instance of the <see cref="IntervalTrigger"/> class. + /// </summary> + /// <param name="interval">The interval.</param> + /// <param name="taskOptions">The options of this task.</param> + public IntervalTrigger(TimeSpan interval, TaskOptions taskOptions) + { + _interval = interval; + TaskOptions = taskOptions; + } + + /// <inheritdoc /> + public event EventHandler<EventArgs>? Triggered; + + /// <inheritdoc /> + public TaskOptions TaskOptions { get; } + + /// <inheritdoc /> + public void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup) + { + DisposeTimer(); + + DateTime now = DateTime.UtcNow; + DateTime triggerDate; + + if (lastResult is null) + { + // Task has never been completed before + triggerDate = now.AddHours(1); + } + else + { + triggerDate = new[] { lastResult.EndTimeUtc, _lastStartDate, now.AddMinutes(1) }.Max().Add(_interval); + } + + var dueTime = triggerDate - now; + var maxDueTime = TimeSpan.FromDays(7); + + if (dueTime > maxDueTime) + { + dueTime = maxDueTime; + } + + _timer = new Timer(_ => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1)); + } + + /// <inheritdoc /> + public void Stop() + { + DisposeTimer(); + } + + /// <summary> + /// Disposes the timer. + /// </summary> + private void DisposeTimer() + { + _timer?.Dispose(); + _timer = null; + } + + /// <summary> + /// Called when [triggered]. + /// </summary> + private void OnTriggered() + { + DisposeTimer(); + + if (Triggered is not null) + { + _lastStartDate = DateTime.UtcNow; + Triggered(this, EventArgs.Empty); + } + } + + /// <inheritdoc /> + public void Dispose() + { + if (_disposed) + { + return; + } + + DisposeTimer(); + + _disposed = true; + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs new file mode 100644 index 00000000..86ceff6c --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs @@ -0,0 +1,53 @@ +using System; +using System.Threading.Tasks; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks.Triggers; + +/// <summary> +/// Class StartupTaskTrigger. +/// </summary> +public sealed class StartupTrigger : ITaskTrigger +{ + private const int DelayMs = 3000; + + /// <summary> + /// Initializes a new instance of the <see cref="StartupTrigger"/> class. + /// </summary> + /// <param name="taskOptions">The options of this task.</param> + public StartupTrigger(TaskOptions taskOptions) + { + TaskOptions = taskOptions; + } + + /// <inheritdoc /> + public event EventHandler<EventArgs>? Triggered; + + /// <inheritdoc /> + public TaskOptions TaskOptions { get; } + + /// <inheritdoc /> + public async void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup) + { + if (isApplicationStartup) + { + await Task.Delay(DelayMs).ConfigureAwait(false); + + OnTriggered(); + } + } + + /// <inheritdoc /> + public void Stop() + { + } + + /// <summary> + /// Called when [triggered]. + /// </summary> + private void OnTriggered() + { + Triggered?.Invoke(this, EventArgs.Empty); + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs new file mode 100644 index 00000000..79568f8a --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs @@ -0,0 +1,109 @@ +using System; +using System.Threading; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks.Triggers; + +/// <summary> +/// Represents a task trigger that fires on a weekly basis. +/// </summary> +public sealed class WeeklyTrigger : ITaskTrigger, IDisposable +{ + private readonly TimeSpan _timeOfDay; + private readonly DayOfWeek _dayOfWeek; + private Timer? _timer; + private bool _disposed; + + /// <summary> + /// Initializes a new instance of the <see cref="WeeklyTrigger"/> class. + /// </summary> + /// <param name="timeOfDay">The time of day to trigger the task to run.</param> + /// <param name="dayOfWeek">The day of week.</param> + /// <param name="taskOptions">The options of this task.</param> + public WeeklyTrigger(TimeSpan timeOfDay, DayOfWeek dayOfWeek, TaskOptions taskOptions) + { + _timeOfDay = timeOfDay; + _dayOfWeek = dayOfWeek; + TaskOptions = taskOptions; + } + + /// <inheritdoc /> + public event EventHandler<EventArgs>? Triggered; + + /// <inheritdoc /> + public TaskOptions TaskOptions { get; } + + /// <inheritdoc /> + public void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup) + { + DisposeTimer(); + + var triggerDate = GetNextTriggerDateTime(); + + _timer = new Timer(_ => OnTriggered(), null, triggerDate - DateTime.Now, TimeSpan.FromMilliseconds(-1)); + } + + /// <summary> + /// Gets the next trigger date time. + /// </summary> + /// <returns>DateTime.</returns> + private DateTime GetNextTriggerDateTime() + { + var now = DateTime.Now; + + // If it's on the same day + if (now.DayOfWeek == _dayOfWeek) + { + // It's either later today, or a week from now + return now.TimeOfDay < _timeOfDay ? now.Date.Add(_timeOfDay) : now.Date.AddDays(7).Add(_timeOfDay); + } + + var triggerDate = now.Date; + + // Walk the date forward until we get to the trigger day + while (triggerDate.DayOfWeek != _dayOfWeek) + { + triggerDate = triggerDate.AddDays(1); + } + + // Return the trigger date plus the time offset + return triggerDate.Add(_timeOfDay); + } + + /// <inheritdoc /> + public void Stop() + { + DisposeTimer(); + } + + /// <summary> + /// Disposes the timer. + /// </summary> + private void DisposeTimer() + { + _timer?.Dispose(); + _timer = null; + } + + /// <summary> + /// Called when [triggered]. + /// </summary> + private void OnTriggered() + { + Triggered?.Invoke(this, EventArgs.Empty); + } + + /// <inheritdoc /> + public void Dispose() + { + if (_disposed) + { + return; + } + + DisposeTimer(); + + _disposed = true; + } +} diff --git a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs new file mode 100644 index 00000000..aa5fbbdf --- /dev/null +++ b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Xml; +using System.Xml.Serialization; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Serialization; + +namespace Emby.Server.Implementations.Serialization +{ + /// <summary> + /// Provides a wrapper around third party xml serialization. + /// </summary> + public class MyXmlSerializer : IXmlSerializer + { + // Need to cache these + // http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html + private readonly ConcurrentDictionary<string, XmlSerializer> _serializers = new(); + + private XmlSerializer GetSerializer(Type type) + => _serializers.GetOrAdd( + type.FullName ?? throw new ArgumentException($"Invalid type {type}."), + static (_, t) => new XmlSerializer(t), + type); + + /// <summary> + /// Serializes to writer. + /// </summary> + /// <param name="obj">The obj.</param> + /// <param name="writer">The writer.</param> + private void SerializeToWriter(object obj, XmlWriter writer) + { + var netSerializer = GetSerializer(obj.GetType()); + netSerializer.Serialize(writer, obj); + } + + /// <summary> + /// Deserializes from stream. + /// </summary> + /// <param name="type">The type.</param> + /// <param name="stream">The stream.</param> + /// <returns>System.Object.</returns> + public object? DeserializeFromStream(Type type, Stream stream) + { + using (var reader = XmlReader.Create(stream)) + { + var netSerializer = GetSerializer(type); + return netSerializer.Deserialize(reader); + } + } + + /// <summary> + /// Serializes to stream. + /// </summary> + /// <param name="obj">The obj.</param> + /// <param name="stream">The stream.</param> + public void SerializeToStream(object obj, Stream stream) + { + using (var writer = new StreamWriter(stream, null, IODefaults.StreamWriterBufferSize, true)) + using (var textWriter = new XmlTextWriter(writer)) + { + textWriter.Formatting = Formatting.Indented; + SerializeToWriter(obj, textWriter); + } + } + + /// <summary> + /// Serializes to file. + /// </summary> + /// <param name="obj">The obj.</param> + /// <param name="file">The file.</param> + public void SerializeToFile(object obj, string file) + { + using (var stream = new FileStream(file, FileMode.Create, FileAccess.Write)) + { + SerializeToStream(obj, stream); + } + } + + /// <summary> + /// Deserializes from file. + /// </summary> + /// <param name="type">The type.</param> + /// <param name="file">The file.</param> + /// <returns>System.Object.</returns> + public object? DeserializeFromFile(Type type, string file) + { + using (var stream = File.OpenRead(file)) + { + return DeserializeFromStream(type, stream); + } + } + + /// <summary> + /// Deserializes from bytes. + /// </summary> + /// <param name="type">The type.</param> + /// <param name="buffer">The buffer.</param> + /// <returns>System.Object.</returns> + public object? DeserializeFromBytes(Type type, byte[] buffer) + { + using (var stream = new MemoryStream(buffer, 0, buffer.Length, false, true)) + { + return DeserializeFromStream(type, stream); + } + } + } +} diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs new file mode 100644 index 00000000..f049e664 --- /dev/null +++ b/Emby.Server.Implementations/ServerApplicationPaths.cs @@ -0,0 +1,107 @@ +using System.IO; +using Emby.Server.Implementations.AppBase; +using MediaBrowser.Controller; + +namespace Emby.Server.Implementations +{ + /// <summary> + /// Extends BaseApplicationPaths to add paths that are only applicable on the server. + /// </summary> + public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths + { + /// <summary> + /// Initializes a new instance of the <see cref="ServerApplicationPaths" /> class. + /// </summary> + /// <param name="programDataPath">The path for Jellyfin's data.</param> + /// <param name="logDirectoryPath">The path for Jellyfin's logging directory.</param> + /// <param name="configurationDirectoryPath">The path for Jellyfin's configuration directory.</param> + /// <param name="cacheDirectoryPath">The path for Jellyfin's cache directory.</param> + /// <param name="webDirectoryPath">The path for Jellyfin's web UI.</param> + public ServerApplicationPaths( + string programDataPath, + string logDirectoryPath, + string configurationDirectoryPath, + string cacheDirectoryPath, + string webDirectoryPath) + : base( + programDataPath, + logDirectoryPath, + configurationDirectoryPath, + cacheDirectoryPath, + webDirectoryPath) + { + // ProgramDataPath cannot change when the server is running, so cache these to avoid allocations. + RootFolderPath = Path.Join(ProgramDataPath, "root"); + DefaultUserViewsPath = Path.Combine(RootFolderPath, "default"); + DefaultInternalMetadataPath = Path.Combine(ProgramDataPath, "metadata"); + InternalMetadataPath = DefaultInternalMetadataPath; + } + + /// <summary> + /// Gets the path to the base root media directory. + /// </summary> + /// <value>The root folder path.</value> + public string RootFolderPath { get; } + + /// <summary> + /// Gets the path to the default user view directory. Used if no specific user view is defined. + /// </summary> + /// <value>The default user views path.</value> + public string DefaultUserViewsPath { get; } + + /// <summary> + /// Gets the path to the People directory. + /// </summary> + /// <value>The people path.</value> + public string PeoplePath => Path.Combine(InternalMetadataPath, "People"); + + /// <inheritdoc /> + public string ArtistsPath => Path.Combine(InternalMetadataPath, "artists"); + + /// <summary> + /// Gets the path to the Genre directory. + /// </summary> + /// <value>The genre path.</value> + public string GenrePath => Path.Combine(InternalMetadataPath, "Genre"); + + /// <summary> + /// Gets the path to the Genre directory. + /// </summary> + /// <value>The genre path.</value> + public string MusicGenrePath => Path.Combine(InternalMetadataPath, "MusicGenre"); + + /// <summary> + /// Gets the path to the Studio directory. + /// </summary> + /// <value>The studio path.</value> + public string StudioPath => Path.Combine(InternalMetadataPath, "Studio"); + + /// <summary> + /// Gets the path to the Year directory. + /// </summary> + /// <value>The year path.</value> + public string YearPath => Path.Combine(InternalMetadataPath, "Year"); + + /// <summary> + /// Gets the path to the user configuration directory. + /// </summary> + /// <value>The user configuration directory path.</value> + public string UserConfigurationDirectoryPath => Path.Combine(ConfigurationDirectoryPath, "users"); + + /// <inheritdoc/> + public string DefaultInternalMetadataPath { get; } + + /// <inheritdoc /> + public string InternalMetadataPath { get; set; } + + /// <inheritdoc /> + public string VirtualInternalMetadataPath => "%MetadataPath%"; + + /// <inheritdoc/> + public override void MakeSanityCheckOrThrow() + { + base.MakeSanityCheckOrThrow(); + CreateAndCheckMarker(RootFolderPath, "root"); + } + } +} diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs new file mode 100644 index 00000000..8e14f5bd --- /dev/null +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -0,0 +1,2154 @@ +#nullable disable + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data; +using Jellyfin.Data.Enums; +using Jellyfin.Data.Events; +using Jellyfin.Data.Queries; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Entities.Security; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Common.Events; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Authentication; +using MediaBrowser.Controller.Events.Session; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Library; +using MediaBrowser.Model.Querying; +using MediaBrowser.Model.Session; +using MediaBrowser.Model.SyncPlay; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; + +namespace Emby.Server.Implementations.Session +{ + /// <summary> + /// Class SessionManager. + /// </summary> + public sealed class SessionManager : ISessionManager, IAsyncDisposable + { + private readonly IUserDataManager _userDataManager; + private readonly IServerConfigurationManager _config; + private readonly ILogger<SessionManager> _logger; + private readonly IEventManager _eventManager; + private readonly ILibraryManager _libraryManager; + private readonly IUserManager _userManager; + private readonly IMusicManager _musicManager; + private readonly IDtoService _dtoService; + private readonly IImageProcessor _imageProcessor; + private readonly IMediaSourceManager _mediaSourceManager; + private readonly IServerApplicationHost _appHost; + private readonly IDeviceManager _deviceManager; + private readonly CancellationTokenRegistration _shutdownCallback; + private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections + = new(StringComparer.OrdinalIgnoreCase); + + private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, string>> _activeLiveStreamSessions + = new(StringComparer.OrdinalIgnoreCase); + + private Timer _idleTimer; + private Timer _inactiveTimer; + + private DtoOptions _itemInfoDtoOptions; + private bool _disposed; + + /// <summary> + /// Initializes a new instance of the <see cref="SessionManager"/> class. + /// </summary> + /// <param name="logger">Instance of <see cref="ILogger{SessionManager}"/> interface.</param> + /// <param name="eventManager">Instance of <see cref="IEventManager"/> interface.</param> + /// <param name="userDataManager">Instance of <see cref="IUserDataManager"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="libraryManager">Instance of <see cref="ILibraryManager"/> interface.</param> + /// <param name="userManager">Instance of <see cref="IUserManager"/> interface.</param> + /// <param name="musicManager">Instance of <see cref="IMusicManager"/> interface.</param> + /// <param name="dtoService">Instance of <see cref="IDtoService"/> interface.</param> + /// <param name="imageProcessor">Instance of <see cref="IImageProcessor"/> interface.</param> + /// <param name="appHost">Instance of <see cref="IServerApplicationHost"/> interface.</param> + /// <param name="deviceManager">Instance of <see cref="IDeviceManager"/> interface.</param> + /// <param name="mediaSourceManager">Instance of <see cref="IMediaSourceManager"/> interface.</param> + /// <param name="hostApplicationLifetime">Instance of <see cref="IHostApplicationLifetime"/> interface.</param> + public SessionManager( + ILogger<SessionManager> logger, + IEventManager eventManager, + IUserDataManager userDataManager, + IServerConfigurationManager serverConfigurationManager, + ILibraryManager libraryManager, + IUserManager userManager, + IMusicManager musicManager, + IDtoService dtoService, + IImageProcessor imageProcessor, + IServerApplicationHost appHost, + IDeviceManager deviceManager, + IMediaSourceManager mediaSourceManager, + IHostApplicationLifetime hostApplicationLifetime) + { + _logger = logger; + _eventManager = eventManager; + _userDataManager = userDataManager; + _config = serverConfigurationManager; + _libraryManager = libraryManager; + _userManager = userManager; + _musicManager = musicManager; + _dtoService = dtoService; + _imageProcessor = imageProcessor; + _appHost = appHost; + _deviceManager = deviceManager; + _mediaSourceManager = mediaSourceManager; + _shutdownCallback = hostApplicationLifetime.ApplicationStopping.Register(OnApplicationStopping); + + _deviceManager.DeviceOptionsUpdated += OnDeviceManagerDeviceOptionsUpdated; + } + + /// <summary> + /// Occurs when playback has started. + /// </summary> + public event EventHandler<PlaybackProgressEventArgs> PlaybackStart; + + /// <summary> + /// Occurs when playback has progressed. + /// </summary> + public event EventHandler<PlaybackProgressEventArgs> PlaybackProgress; + + /// <summary> + /// Occurs when playback has stopped. + /// </summary> + public event EventHandler<PlaybackStopEventArgs> PlaybackStopped; + + /// <inheritdoc /> + public event EventHandler<SessionEventArgs> SessionStarted; + + /// <inheritdoc /> + public event EventHandler<SessionEventArgs> CapabilitiesChanged; + + /// <inheritdoc /> + public event EventHandler<SessionEventArgs> SessionEnded; + + /// <inheritdoc /> + public event EventHandler<SessionEventArgs> SessionActivity; + + /// <inheritdoc /> + public event EventHandler<SessionEventArgs> SessionControllerConnected; + + /// <summary> + /// Gets all connections. + /// </summary> + /// <value>All connections.</value> + public IEnumerable<SessionInfo> Sessions => _activeConnections.Values.OrderByDescending(c => c.LastActivityDate); + + private void OnDeviceManagerDeviceOptionsUpdated(object sender, GenericEventArgs<Tuple<string, DeviceOptions>> e) + { + foreach (var session in Sessions) + { + if (string.Equals(session.DeviceId, e.Argument.Item1, StringComparison.Ordinal)) + { + if (!string.IsNullOrWhiteSpace(e.Argument.Item2.CustomName)) + { + session.HasCustomDeviceName = true; + session.DeviceName = e.Argument.Item2.CustomName; + } + else + { + session.HasCustomDeviceName = false; + } + } + } + } + + private void CheckDisposed() + { + ObjectDisposedException.ThrowIf(_disposed, this); + } + + private void OnSessionStarted(SessionInfo info) + { + if (!string.IsNullOrEmpty(info.DeviceId)) + { + var capabilities = _deviceManager.GetCapabilities(info.DeviceId); + + if (capabilities is not null) + { + ReportCapabilities(info, capabilities, false); + } + } + + _eventManager.Publish(new SessionStartedEventArgs(info)); + + EventHelper.QueueEventIfNotNull( + SessionStarted, + this, + new SessionEventArgs + { + SessionInfo = info + }, + _logger); + } + + private async ValueTask OnSessionEnded(SessionInfo info) + { + EventHelper.QueueEventIfNotNull( + SessionEnded, + this, + new SessionEventArgs + { + SessionInfo = info + }, + _logger); + + _eventManager.Publish(new SessionEndedEventArgs(info)); + + await info.DisposeAsync().ConfigureAwait(false); + } + + /// <inheritdoc /> + public void UpdateDeviceName(string sessionId, string reportedDeviceName) + { + var session = GetSession(sessionId); + if (session is not null) + { + session.DeviceName = reportedDeviceName; + } + } + + /// <summary> + /// Logs the user activity. + /// </summary> + /// <param name="appName">Type of the client.</param> + /// <param name="appVersion">The app version.</param> + /// <param name="deviceId">The device id.</param> + /// <param name="deviceName">Name of the device.</param> + /// <param name="remoteEndPoint">The remote end point.</param> + /// <param name="user">The user.</param> + /// <returns>SessionInfo.</returns> + public async Task<SessionInfo> LogSessionActivity( + string appName, + string appVersion, + string deviceId, + string deviceName, + string remoteEndPoint, + User user) + { + CheckDisposed(); + + ArgumentException.ThrowIfNullOrEmpty(appName); + ArgumentException.ThrowIfNullOrEmpty(appVersion); + ArgumentException.ThrowIfNullOrEmpty(deviceId); + + var activityDate = DateTime.UtcNow; + var session = GetSessionInfo(appName, appVersion, deviceId, deviceName, remoteEndPoint, user); + var lastActivityDate = session.LastActivityDate; + session.LastActivityDate = activityDate; + + if (user is not null) + { + var userLastActivityDate = user.LastActivityDate ?? DateTime.MinValue; + + if ((activityDate - userLastActivityDate).TotalSeconds > 60) + { + try + { + user.LastActivityDate = activityDate; + await _userManager.UpdateUserAsync(user).ConfigureAwait(false); + } + catch (DbUpdateConcurrencyException e) + { + _logger.LogDebug(e, "Error updating user's last activity date."); + } + } + } + + if ((activityDate - lastActivityDate).TotalSeconds > 10) + { + SessionActivity?.Invoke( + this, + new SessionEventArgs + { + SessionInfo = session + }); + } + + return session; + } + + /// <inheritdoc /> + public void OnSessionControllerConnected(SessionInfo session) + { + EventHelper.QueueEventIfNotNull( + SessionControllerConnected, + this, + new SessionEventArgs + { + SessionInfo = session + }, + _logger); + } + + /// <inheritdoc /> + public async Task CloseIfNeededAsync(SessionInfo session) + { + if (!session.SessionControllers.Any(i => i.IsSessionActive)) + { + var key = GetSessionKey(session.Client, session.DeviceId); + + _activeConnections.TryRemove(key, out _); + if (!string.IsNullOrEmpty(session.PlayState?.LiveStreamId)) + { + await CloseLiveStreamIfNeededAsync(session.PlayState.LiveStreamId, session.Id).ConfigureAwait(false); + } + + await OnSessionEnded(session).ConfigureAwait(false); + } + } + + /// <inheritdoc /> + public async Task CloseLiveStreamIfNeededAsync(string liveStreamId, string sessionIdOrPlaySessionId) + { + bool liveStreamNeedsToBeClosed = false; + + if (_activeLiveStreamSessions.TryGetValue(liveStreamId, out var activeSessionMappings)) + { + if (activeSessionMappings.TryRemove(sessionIdOrPlaySessionId, out var correspondingId)) + { + if (!string.IsNullOrEmpty(correspondingId)) + { + activeSessionMappings.TryRemove(correspondingId, out _); + } + + liveStreamNeedsToBeClosed = true; + } + + if (activeSessionMappings.IsEmpty) + { + _activeLiveStreamSessions.TryRemove(liveStreamId, out _); + } + } + + if (liveStreamNeedsToBeClosed) + { + try + { + await _mediaSourceManager.CloseLiveStream(liveStreamId).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error closing live stream"); + } + } + } + + /// <inheritdoc /> + public async ValueTask ReportSessionEnded(string sessionId) + { + CheckDisposed(); + var session = GetSession(sessionId, false); + + if (session is not null) + { + var key = GetSessionKey(session.Client, session.DeviceId); + + _activeConnections.TryRemove(key, out _); + + await OnSessionEnded(session).ConfigureAwait(false); + } + } + + private Task<MediaSourceInfo> GetMediaSource(BaseItem item, string mediaSourceId, string liveStreamId) + { + return _mediaSourceManager.GetMediaSource(item, mediaSourceId, liveStreamId, false, CancellationToken.None); + } + + /// <summary> + /// Updates the now playing item id. + /// </summary> + /// <returns>Task.</returns> + private async Task UpdateNowPlayingItem(SessionInfo session, PlaybackProgressInfo info, BaseItem libraryItem, bool updateLastCheckInTime) + { + if (session is null) + { + return; + } + + if (string.IsNullOrEmpty(info.MediaSourceId)) + { + info.MediaSourceId = info.ItemId.ToString("N", CultureInfo.InvariantCulture); + } + + if (!info.ItemId.IsEmpty() && info.Item is null && libraryItem is not null) + { + var current = session.NowPlayingItem; + + if (current is null || !info.ItemId.Equals(current.Id)) + { + var runtimeTicks = libraryItem.RunTimeTicks; + + MediaSourceInfo mediaSource = null; + if (libraryItem is IHasMediaSources) + { + mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); + + if (mediaSource is not null) + { + runtimeTicks = mediaSource.RunTimeTicks; + } + } + + info.Item = GetItemInfo(libraryItem, mediaSource); + + info.Item.RunTimeTicks = runtimeTicks; + } + else + { + info.Item = current; + } + } + + session.NowPlayingItem = info.Item; + session.LastActivityDate = DateTime.UtcNow; + + if (updateLastCheckInTime) + { + session.LastPlaybackCheckIn = DateTime.UtcNow; + } + + if (info.IsPaused && session.LastPausedDate is null) + { + session.LastPausedDate = DateTime.UtcNow; + } + else if (!info.IsPaused) + { + session.LastPausedDate = null; + } + + session.PlayState.IsPaused = info.IsPaused; + session.PlayState.PositionTicks = info.PositionTicks; + session.PlayState.MediaSourceId = info.MediaSourceId; + session.PlayState.LiveStreamId = info.LiveStreamId; + session.PlayState.CanSeek = info.CanSeek; + session.PlayState.IsMuted = info.IsMuted; + session.PlayState.VolumeLevel = info.VolumeLevel; + session.PlayState.AudioStreamIndex = info.AudioStreamIndex; + session.PlayState.SubtitleStreamIndex = info.SubtitleStreamIndex; + session.PlayState.PlayMethod = info.PlayMethod; + session.PlayState.RepeatMode = info.RepeatMode; + session.PlayState.PlaybackOrder = info.PlaybackOrder; + session.PlaylistItemId = info.PlaylistItemId; + + var nowPlayingQueue = info.NowPlayingQueue; + + if (nowPlayingQueue?.Length > 0 && !nowPlayingQueue.SequenceEqual(session.NowPlayingQueue)) + { + session.NowPlayingQueue = nowPlayingQueue; + + var itemIds = Array.ConvertAll(nowPlayingQueue, queue => queue.Id); + session.NowPlayingQueueFullItems = _dtoService.GetBaseItemDtos( + _libraryManager.GetItemList(new InternalItemsQuery { ItemIds = itemIds }), + new DtoOptions(true)); + } + } + + /// <summary> + /// Removes the now playing item id. + /// </summary> + /// <param name="session">The session.</param> + private void RemoveNowPlayingItem(SessionInfo session) + { + session.NowPlayingItem = null; + session.FullNowPlayingItem = null; + session.PlayState = new PlayerStateInfo(); + + if (!string.IsNullOrEmpty(session.DeviceId)) + { + ClearTranscodingInfo(session.DeviceId); + } + } + + private static string GetSessionKey(string appName, string deviceId) + => appName + deviceId; + + /// <summary> + /// Gets the connection. + /// </summary> + /// <param name="appName">Type of the client.</param> + /// <param name="appVersion">The app version.</param> + /// <param name="deviceId">The device id.</param> + /// <param name="deviceName">Name of the device.</param> + /// <param name="remoteEndPoint">The remote end point.</param> + /// <param name="user">The user.</param> + /// <returns>SessionInfo.</returns> + private SessionInfo GetSessionInfo( + string appName, + string appVersion, + string deviceId, + string deviceName, + string remoteEndPoint, + User user) + { + CheckDisposed(); + + ArgumentException.ThrowIfNullOrEmpty(deviceId); + + var key = GetSessionKey(appName, deviceId); + SessionInfo newSession = CreateSessionInfo(key, appName, appVersion, deviceId, deviceName, remoteEndPoint, user); + SessionInfo sessionInfo = _activeConnections.GetOrAdd(key, newSession); + if (ReferenceEquals(newSession, sessionInfo)) + { + OnSessionStarted(newSession); + } + + sessionInfo.UserId = user?.Id ?? Guid.Empty; + sessionInfo.UserName = user?.Username; + sessionInfo.UserPrimaryImageTag = user?.ProfileImage is null ? null : GetImageCacheTag(user); + sessionInfo.RemoteEndPoint = remoteEndPoint; + sessionInfo.Client = appName; + + if (!sessionInfo.HasCustomDeviceName || string.IsNullOrEmpty(sessionInfo.DeviceName)) + { + sessionInfo.DeviceName = deviceName; + } + + sessionInfo.ApplicationVersion = appVersion; + + if (user is null) + { + sessionInfo.AdditionalUsers = Array.Empty<SessionUserInfo>(); + } + + return sessionInfo; + } + + private SessionInfo CreateSessionInfo( + string key, + string appName, + string appVersion, + string deviceId, + string deviceName, + string remoteEndPoint, + User user) + { + var sessionInfo = new SessionInfo(this, _logger) + { + Client = appName, + DeviceId = deviceId, + ApplicationVersion = appVersion, + Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture), + ServerId = _appHost.SystemId + }; + + var username = user?.Username; + + sessionInfo.UserId = user?.Id ?? Guid.Empty; + sessionInfo.UserName = username; + sessionInfo.UserPrimaryImageTag = user?.ProfileImage is null ? null : GetImageCacheTag(user); + sessionInfo.RemoteEndPoint = remoteEndPoint; + + if (string.IsNullOrEmpty(deviceName)) + { + deviceName = "Network Device"; + } + + var deviceOptions = _deviceManager.GetDeviceOptions(deviceId) ?? new() + { + DeviceId = deviceId + }; + if (string.IsNullOrEmpty(deviceOptions.CustomName)) + { + sessionInfo.DeviceName = deviceName; + } + else + { + sessionInfo.DeviceName = deviceOptions.CustomName; + sessionInfo.HasCustomDeviceName = true; + } + + return sessionInfo; + } + + private List<User> GetUsers(SessionInfo session) + { + var users = new List<User>(); + + if (session.UserId.IsEmpty()) + { + return users; + } + + var user = _userManager.GetUserById(session.UserId); + + if (user is null) + { + throw new InvalidOperationException("User not found"); + } + + users.Add(user); + + users.AddRange(session.AdditionalUsers + .Select(i => _userManager.GetUserById(i.UserId)) + .Where(i => i is not null)); + + return users; + } + + private void StartCheckTimers() + { + _idleTimer ??= new Timer(CheckForIdlePlayback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); + + if (_config.Configuration.InactiveSessionThreshold > 0) + { + _inactiveTimer ??= new Timer(CheckForInactiveSteams, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); + } + else + { + StopInactiveCheckTimer(); + } + } + + private void StopIdleCheckTimer() + { + if (_idleTimer is not null) + { + _idleTimer.Dispose(); + _idleTimer = null; + } + } + + private void StopInactiveCheckTimer() + { + if (_inactiveTimer is not null) + { + _inactiveTimer.Dispose(); + _inactiveTimer = null; + } + } + + private async void CheckForIdlePlayback(object state) + { + var playingSessions = Sessions.Where(i => i.NowPlayingItem is not null) + .ToList(); + + if (playingSessions.Count > 0) + { + var idle = playingSessions + .Where(i => (DateTime.UtcNow - i.LastPlaybackCheckIn).TotalMinutes > 5) + .ToList(); + + foreach (var session in idle) + { + _logger.LogDebug("Session {0} has gone idle while playing", session.Id); + + try + { + await OnPlaybackStopped(new PlaybackStopInfo + { + Item = session.NowPlayingItem, + ItemId = session.NowPlayingItem is null ? Guid.Empty : session.NowPlayingItem.Id, + SessionId = session.Id, + MediaSourceId = session.PlayState?.MediaSourceId, + PositionTicks = session.PlayState?.PositionTicks + }).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Error calling OnPlaybackStopped"); + } + } + } + else + { + StopIdleCheckTimer(); + } + } + + private async void CheckForInactiveSteams(object state) + { + var inactiveSessions = Sessions.Where(i => + i.NowPlayingItem is not null + && i.PlayState.IsPaused + && (DateTime.UtcNow - i.LastPausedDate).Value.TotalMinutes > _config.Configuration.InactiveSessionThreshold); + + foreach (var session in inactiveSessions) + { + _logger.LogDebug("Session {Session} has been inactive for {InactiveTime} minutes. Stopping it.", session.Id, _config.Configuration.InactiveSessionThreshold); + + try + { + await SendPlaystateCommand( + session.Id, + session.Id, + new PlaystateRequest() + { + Command = PlaystateCommand.Stop, + ControllingUserId = session.UserId.ToString(), + SeekPositionTicks = session.PlayState?.PositionTicks + }, + CancellationToken.None).ConfigureAwait(true); + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Error calling SendPlaystateCommand for stopping inactive session {Session}.", session.Id); + } + } + + bool playingSessions = Sessions.Any(i => i.NowPlayingItem is not null); + + if (!playingSessions) + { + StopInactiveCheckTimer(); + } + } + + private BaseItem GetNowPlayingItem(SessionInfo session, Guid itemId) + { + if (session is null) + { + return null; + } + + var item = session.FullNowPlayingItem; + if (item is not null && item.Id.Equals(itemId)) + { + return item; + } + + item = _libraryManager.GetItemById(itemId); + + session.FullNowPlayingItem = item; + + return item; + } + + /// <summary> + /// Used to report that playback has started for an item. + /// </summary> + /// <param name="info">The info.</param> + /// <returns>Task.</returns> + /// <exception cref="ArgumentNullException"><c>info</c> is <c>null</c>.</exception> + public async Task OnPlaybackStart(PlaybackStartInfo info) + { + CheckDisposed(); + + ArgumentNullException.ThrowIfNull(info); + + var session = GetSession(info.SessionId); + + var libraryItem = info.ItemId.IsEmpty() + ? null + : GetNowPlayingItem(session, info.ItemId); + + await UpdateNowPlayingItem(session, info, libraryItem, true).ConfigureAwait(false); + + if (!string.IsNullOrEmpty(session.DeviceId) && info.PlayMethod != PlayMethod.Transcode) + { + ClearTranscodingInfo(session.DeviceId); + } + + session.StartAutomaticProgress(info); + + var users = GetUsers(session); + + if (libraryItem is not null) + { + foreach (var user in users) + { + OnPlaybackStart(user, libraryItem); + } + } + + if (!string.IsNullOrEmpty(info.LiveStreamId)) + { + UpdateLiveStreamActiveSessionMappings(info.LiveStreamId, info.SessionId, info.PlaySessionId); + } + + var eventArgs = new PlaybackStartEventArgs + { + Item = libraryItem, + Users = users, + MediaSourceId = info.MediaSourceId, + MediaInfo = info.Item, + DeviceName = session.DeviceName, + ClientName = session.Client, + DeviceId = session.DeviceId, + Session = session, + PlaybackPositionTicks = info.PositionTicks, + PlaySessionId = info.PlaySessionId + }; + + if (info.Item is not null) + { + _logger.LogInformation( + "User {0} started playback of '{1}' ({2} {3})", + session.UserName, + info.Item.Name, + session.Client, + session.ApplicationVersion); + } + + await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false); + + // Nothing to save here + // Fire events to inform plugins + EventHelper.QueueEventIfNotNull( + PlaybackStart, + this, + eventArgs, + _logger); + + StartCheckTimers(); + } + + /// <summary> + /// Called when [playback start]. + /// </summary> + /// <param name="user">The user object.</param> + /// <param name="item">The item.</param> + private void OnPlaybackStart(User user, BaseItem item) + { + var data = _userDataManager.GetUserData(user, item); + + data.PlayCount++; + data.LastPlayedDate = DateTime.UtcNow; + + if (item.SupportsPlayedStatus && !item.SupportsPositionTicksResume) + { + data.Played = true; + } + else + { + data.Played = false; + } + + _userDataManager.SaveUserData(user, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None); + } + + /// <inheritdoc /> + public Task OnPlaybackProgress(PlaybackProgressInfo info) + { + return OnPlaybackProgress(info, false); + } + + private void UpdateLiveStreamActiveSessionMappings(string liveStreamId, string sessionId, string playSessionId) + { + var activeSessionMappings = _activeLiveStreamSessions.GetOrAdd(liveStreamId, _ => new ConcurrentDictionary<string, string>()); + + if (!string.IsNullOrEmpty(playSessionId)) + { + if (!activeSessionMappings.TryGetValue(sessionId, out var currentPlaySessionId) || currentPlaySessionId != playSessionId) + { + if (!string.IsNullOrEmpty(currentPlaySessionId)) + { + activeSessionMappings.TryRemove(currentPlaySessionId, out _); + } + + activeSessionMappings[sessionId] = playSessionId; + activeSessionMappings[playSessionId] = sessionId; + } + } + else + { + if (!activeSessionMappings.TryGetValue(sessionId, out _)) + { + activeSessionMappings[sessionId] = string.Empty; + } + } + } + + /// <summary> + /// Used to report playback progress for an item. + /// </summary> + /// <param name="info">The playback progress info.</param> + /// <param name="isAutomated">Whether this is an automated update.</param> + /// <returns>Task.</returns> + public async Task OnPlaybackProgress(PlaybackProgressInfo info, bool isAutomated) + { + CheckDisposed(); + + ArgumentNullException.ThrowIfNull(info); + + var session = GetSession(info.SessionId, false); + if (session is null) + { + return; + } + + var libraryItem = info.ItemId.IsEmpty() + ? null + : GetNowPlayingItem(session, info.ItemId); + + await UpdateNowPlayingItem(session, info, libraryItem, !isAutomated).ConfigureAwait(false); + + if (!string.IsNullOrEmpty(session.DeviceId) && info.PlayMethod != PlayMethod.Transcode) + { + ClearTranscodingInfo(session.DeviceId); + } + + var users = GetUsers(session); + + // only update saved user data on actual check-ins, not automated ones + if (libraryItem is not null && !isAutomated) + { + foreach (var user in users) + { + OnPlaybackProgress(user, libraryItem, info); + } + } + + if (!string.IsNullOrEmpty(info.LiveStreamId)) + { + UpdateLiveStreamActiveSessionMappings(info.LiveStreamId, info.SessionId, info.PlaySessionId); + } + + var eventArgs = new PlaybackProgressEventArgs + { + Item = libraryItem, + Users = users, + PlaybackPositionTicks = session.PlayState.PositionTicks, + MediaSourceId = session.PlayState.MediaSourceId, + MediaInfo = info.Item, + DeviceName = session.DeviceName, + ClientName = session.Client, + DeviceId = session.DeviceId, + IsPaused = info.IsPaused, + PlaySessionId = info.PlaySessionId, + IsAutomated = isAutomated, + Session = session + }; + + await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false); + + PlaybackProgress?.Invoke(this, eventArgs); + + if (!isAutomated) + { + session.StartAutomaticProgress(info); + } + + StartCheckTimers(); + } + + private void OnPlaybackProgress(User user, BaseItem item, PlaybackProgressInfo info) + { + var data = _userDataManager.GetUserData(user, item); + + var positionTicks = info.PositionTicks; + + var changed = false; + + if (positionTicks.HasValue) + { + _userDataManager.UpdatePlayState(item, data, positionTicks.Value); + changed = true; + } + + var tracksChanged = UpdatePlaybackSettings(user, info, data); + if (!tracksChanged) + { + changed = true; + } + + if (changed) + { + _userDataManager.SaveUserData(user, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None); + } + } + + private static bool UpdatePlaybackSettings(User user, PlaybackProgressInfo info, UserItemData data) + { + var changed = false; + + if (user.RememberAudioSelections) + { + if (data.AudioStreamIndex != info.AudioStreamIndex) + { + data.AudioStreamIndex = info.AudioStreamIndex; + changed = true; + } + } + else + { + if (data.AudioStreamIndex.HasValue) + { + data.AudioStreamIndex = null; + changed = true; + } + } + + if (user.RememberSubtitleSelections) + { + if (data.SubtitleStreamIndex != info.SubtitleStreamIndex) + { + data.SubtitleStreamIndex = info.SubtitleStreamIndex; + changed = true; + } + } + else + { + if (data.SubtitleStreamIndex.HasValue) + { + data.SubtitleStreamIndex = null; + changed = true; + } + } + + return changed; + } + + /// <summary> + /// Used to report that playback has ended for an item. + /// </summary> + /// <param name="info">The info.</param> + /// <returns>Task.</returns> + /// <exception cref="ArgumentNullException"><c>info</c> is <c>null</c>.</exception> + /// <exception cref="ArgumentOutOfRangeException"><c>info.PositionTicks</c> is <c>null</c> or negative.</exception> + public async Task OnPlaybackStopped(PlaybackStopInfo info) + { + CheckDisposed(); + + ArgumentNullException.ThrowIfNull(info); + + if (info.PositionTicks.HasValue && info.PositionTicks.Value < 0) + { + throw new ArgumentOutOfRangeException(nameof(info), "The PlaybackStopInfo's PositionTicks was negative."); + } + + var session = GetSession(info.SessionId); + + session.StopAutomaticProgress(); + + var libraryItem = info.ItemId.IsEmpty() + ? null + : GetNowPlayingItem(session, info.ItemId); + + // Normalize + if (string.IsNullOrEmpty(info.MediaSourceId)) + { + info.MediaSourceId = info.ItemId.ToString("N", CultureInfo.InvariantCulture); + } + + if (!info.ItemId.IsEmpty() && info.Item is null && libraryItem is not null) + { + var current = session.NowPlayingItem; + + if (current is null || !info.ItemId.Equals(current.Id)) + { + MediaSourceInfo mediaSource = null; + + if (libraryItem is IHasMediaSources) + { + mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); + } + + info.Item = GetItemInfo(libraryItem, mediaSource); + } + else + { + info.Item = current; + } + } + + if (info.Item is not null) + { + var msString = info.PositionTicks.HasValue ? (info.PositionTicks.Value / 10000).ToString(CultureInfo.InvariantCulture) : "unknown"; + + _logger.LogInformation( + "User {0} stopped playback of '{1}' at {2}ms ({3} {4})", + session.UserName, + info.Item.Name, + msString, + session.Client, + session.ApplicationVersion); + } + + if (info.NowPlayingQueue is not null) + { + session.NowPlayingQueue = info.NowPlayingQueue; + } + + session.PlaylistItemId = info.PlaylistItemId; + + RemoveNowPlayingItem(session); + + var users = GetUsers(session); + var playedToCompletion = false; + + if (libraryItem is not null) + { + foreach (var user in users) + { + playedToCompletion = OnPlaybackStopped(user, libraryItem, info.PositionTicks, info.Failed); + } + } + + if (!string.IsNullOrEmpty(info.LiveStreamId)) + { + await CloseLiveStreamIfNeededAsync(info.LiveStreamId, session.Id).ConfigureAwait(false); + } + + var eventArgs = new PlaybackStopEventArgs + { + Item = libraryItem, + Users = users, + PlaybackPositionTicks = info.PositionTicks, + PlayedToCompletion = playedToCompletion, + MediaSourceId = info.MediaSourceId, + MediaInfo = info.Item, + DeviceName = session.DeviceName, + ClientName = session.Client, + DeviceId = session.DeviceId, + Session = session, + PlaySessionId = info.PlaySessionId + }; + + await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false); + + EventHelper.QueueEventIfNotNull(PlaybackStopped, this, eventArgs, _logger); + } + + private bool OnPlaybackStopped(User user, BaseItem item, long? positionTicks, bool playbackFailed) + { + if (playbackFailed) + { + return false; + } + + var data = _userDataManager.GetUserData(user, item); + bool playedToCompletion; + if (positionTicks.HasValue) + { + playedToCompletion = _userDataManager.UpdatePlayState(item, data, positionTicks.Value); + } + else + { + // If the client isn't able to report this, then we'll just have to make an assumption + data.PlayCount++; + data.Played = item.SupportsPlayedStatus; + data.PlaybackPositionTicks = 0; + playedToCompletion = true; + } + + _userDataManager.SaveUserData(user, item, data, UserDataSaveReason.PlaybackFinished, CancellationToken.None); + + return playedToCompletion; + } + + /// <summary> + /// Gets the session. + /// </summary> + /// <param name="sessionId">The session identifier.</param> + /// <param name="throwOnMissing">if set to <c>true</c> [throw on missing].</param> + /// <returns>SessionInfo.</returns> + /// <exception cref="ResourceNotFoundException"> + /// No session with an Id equal to <c>sessionId</c> was found + /// and <c>throwOnMissing</c> is <c>true</c>. + /// </exception> + private SessionInfo GetSession(string sessionId, bool throwOnMissing = true) + { + var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId, StringComparison.Ordinal)); + if (session is null && throwOnMissing) + { + throw new ResourceNotFoundException( + string.Format(CultureInfo.InvariantCulture, "Session {0} not found.", sessionId)); + } + + return session; + } + + private SessionInfo GetSessionToRemoteControl(string sessionId) + { + // Accept either device id or session id + var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId, StringComparison.Ordinal)); + + if (session is null) + { + throw new ResourceNotFoundException( + string.Format(CultureInfo.InvariantCulture, "Session {0} not found.", sessionId)); + } + + return session; + } + + /// <inheritdoc /> + public SessionInfoDto ToSessionInfoDto(SessionInfo sessionInfo) + { + return new SessionInfoDto + { + PlayState = sessionInfo.PlayState, + AdditionalUsers = sessionInfo.AdditionalUsers, + Capabilities = _deviceManager.ToClientCapabilitiesDto(sessionInfo.Capabilities), + RemoteEndPoint = sessionInfo.RemoteEndPoint, + PlayableMediaTypes = sessionInfo.PlayableMediaTypes, + Id = sessionInfo.Id, + UserId = sessionInfo.UserId, + UserName = sessionInfo.UserName, + Client = sessionInfo.Client, + LastActivityDate = sessionInfo.LastActivityDate, + LastPlaybackCheckIn = sessionInfo.LastPlaybackCheckIn, + LastPausedDate = sessionInfo.LastPausedDate, + DeviceName = sessionInfo.DeviceName, + DeviceType = sessionInfo.DeviceType, + NowPlayingItem = sessionInfo.NowPlayingItem, + NowViewingItem = sessionInfo.NowViewingItem, + DeviceId = sessionInfo.DeviceId, + ApplicationVersion = sessionInfo.ApplicationVersion, + TranscodingInfo = sessionInfo.TranscodingInfo, + IsActive = sessionInfo.IsActive, + SupportsMediaControl = sessionInfo.SupportsMediaControl, + SupportsRemoteControl = sessionInfo.SupportsRemoteControl, + NowPlayingQueue = sessionInfo.NowPlayingQueue, + NowPlayingQueueFullItems = sessionInfo.NowPlayingQueueFullItems, + HasCustomDeviceName = sessionInfo.HasCustomDeviceName, + PlaylistItemId = sessionInfo.PlaylistItemId, + ServerId = sessionInfo.ServerId, + UserPrimaryImageTag = sessionInfo.UserPrimaryImageTag, + SupportedCommands = sessionInfo.SupportedCommands + }; + } + + /// <inheritdoc /> + public Task SendMessageCommand(string controllingSessionId, string sessionId, MessageCommand command, CancellationToken cancellationToken) + { + CheckDisposed(); + + var generalCommand = new GeneralCommand + { + Name = GeneralCommandType.DisplayMessage + }; + + generalCommand.Arguments["Header"] = command.Header; + generalCommand.Arguments["Text"] = command.Text; + + if (command.TimeoutMs.HasValue) + { + generalCommand.Arguments["TimeoutMs"] = command.TimeoutMs.Value.ToString(CultureInfo.InvariantCulture); + } + + return SendGeneralCommand(controllingSessionId, sessionId, generalCommand, cancellationToken); + } + + /// <inheritdoc /> + public Task SendGeneralCommand(string controllingSessionId, string sessionId, GeneralCommand command, CancellationToken cancellationToken) + { + CheckDisposed(); + + var session = GetSessionToRemoteControl(sessionId); + + if (!string.IsNullOrEmpty(controllingSessionId)) + { + var controllingSession = GetSession(controllingSessionId); + AssertCanControl(session, controllingSession); + } + + return SendMessageToSession(session, SessionMessageType.GeneralCommand, command, cancellationToken); + } + + private static async Task SendMessageToSession<T>(SessionInfo session, SessionMessageType name, T data, CancellationToken cancellationToken) + { + var controllers = session.SessionControllers; + var messageId = Guid.NewGuid(); + + foreach (var controller in controllers) + { + await controller.SendMessage(name, messageId, data, cancellationToken).ConfigureAwait(false); + } + } + + private static Task SendMessageToSessions<T>(IEnumerable<SessionInfo> sessions, SessionMessageType name, T data, CancellationToken cancellationToken) + { + IEnumerable<Task> GetTasks() + { + var messageId = Guid.NewGuid(); + foreach (var session in sessions) + { + var controllers = session.SessionControllers; + foreach (var controller in controllers) + { + yield return controller.SendMessage(name, messageId, data, cancellationToken); + } + } + } + + return Task.WhenAll(GetTasks()); + } + + /// <inheritdoc /> + public async Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken) + { + CheckDisposed(); + + var session = GetSessionToRemoteControl(sessionId); + + var user = session.UserId.IsEmpty() ? null : _userManager.GetUserById(session.UserId); + + List<BaseItem> items; + + if (command.PlayCommand == PlayCommand.PlayInstantMix) + { + items = command.ItemIds.SelectMany(i => TranslateItemForInstantMix(i, user)) + .ToList(); + + command.PlayCommand = PlayCommand.PlayNow; + } + else + { + var list = new List<BaseItem>(); + foreach (var itemId in command.ItemIds) + { + var subItems = TranslateItemForPlayback(itemId, user); + list.AddRange(subItems); + } + + items = list; + } + + if (command.PlayCommand == PlayCommand.PlayShuffle) + { + items.Shuffle(); + command.PlayCommand = PlayCommand.PlayNow; + } + + command.ItemIds = items.Select(i => i.Id).ToArray(); + + if (user is not null) + { + if (items.Any(i => i.GetPlayAccess(user) != PlayAccess.Full)) + { + throw new ArgumentException( + string.Format(CultureInfo.InvariantCulture, "{0} is not allowed to play media.", user.Username)); + } + } + + if (user is not null + && command.ItemIds.Length == 1 + && user.EnableNextEpisodeAutoPlay + && _libraryManager.GetItemById(command.ItemIds[0]) is Episode episode) + { + var series = episode.Series; + if (series is not null) + { + var episodes = series.GetEpisodes( + user, + new DtoOptions(false) + { + EnableImages = false + }, + user.DisplayMissingEpisodes) + .Where(i => !i.IsVirtualItem) + .SkipWhile(i => !i.Id.Equals(episode.Id)) + .ToList(); + + if (episodes.Count > 0) + { + command.ItemIds = episodes.Select(i => i.Id).ToArray(); + } + } + } + + if (!string.IsNullOrEmpty(controllingSessionId)) + { + var controllingSession = GetSession(controllingSessionId); + AssertCanControl(session, controllingSession); + if (!controllingSession.UserId.IsEmpty()) + { + command.ControllingUserId = controllingSession.UserId; + } + } + + await SendMessageToSession(session, SessionMessageType.Play, command, cancellationToken).ConfigureAwait(false); + } + + /// <inheritdoc /> + public async Task SendSyncPlayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken) + { + CheckDisposed(); + var session = GetSession(sessionId); + await SendMessageToSession(session, SessionMessageType.SyncPlayCommand, command, cancellationToken).ConfigureAwait(false); + } + + /// <inheritdoc /> + public async Task SendSyncPlayGroupUpdate<T>(string sessionId, GroupUpdate<T> command, CancellationToken cancellationToken) + { + CheckDisposed(); + var session = GetSession(sessionId); + await SendMessageToSession(session, SessionMessageType.SyncPlayGroupUpdate, command, cancellationToken).ConfigureAwait(false); + } + + private IEnumerable<BaseItem> TranslateItemForPlayback(Guid id, User user) + { + var item = _libraryManager.GetItemById(id); + + if (item is null) + { + _logger.LogError("A nonexistent item Id {0} was passed into TranslateItemForPlayback", id); + return Array.Empty<BaseItem>(); + } + + if (item is IItemByName byName) + { + return byName.GetTaggedItems(new InternalItemsQuery(user) + { + IsFolder = false, + Recursive = true, + DtoOptions = new DtoOptions(false) + { + EnableImages = false, + Fields = new[] + { + ItemFields.SortName + } + }, + IsVirtualItem = false, + OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) } + }); + } + + if (item.IsFolder) + { + var folder = (Folder)item; + + return folder.GetItemList(new InternalItemsQuery(user) + { + Recursive = true, + IsFolder = false, + DtoOptions = new DtoOptions(false) + { + EnableImages = false, + Fields = new ItemFields[] + { + ItemFields.SortName + } + }, + IsVirtualItem = false, + OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) } + }); + } + + return new[] { item }; + } + + private List<BaseItem> TranslateItemForInstantMix(Guid id, User user) + { + var item = _libraryManager.GetItemById(id); + + if (item is null) + { + _logger.LogError("A nonexistent item Id {0} was passed into TranslateItemForInstantMix", id); + return new List<BaseItem>(); + } + + return _musicManager.GetInstantMixFromItem(item, user, new DtoOptions(false) { EnableImages = false }).ToList(); + } + + /// <inheritdoc /> + public Task SendBrowseCommand(string controllingSessionId, string sessionId, BrowseRequest command, CancellationToken cancellationToken) + { + var generalCommand = new GeneralCommand + { + Name = GeneralCommandType.DisplayContent, + Arguments = + { + ["ItemId"] = command.ItemId, + ["ItemName"] = command.ItemName, + ["ItemType"] = command.ItemType.ToString() + } + }; + + return SendGeneralCommand(controllingSessionId, sessionId, generalCommand, cancellationToken); + } + + /// <inheritdoc /> + public Task SendPlaystateCommand(string controllingSessionId, string sessionId, PlaystateRequest command, CancellationToken cancellationToken) + { + CheckDisposed(); + + var session = GetSessionToRemoteControl(sessionId); + + if (!string.IsNullOrEmpty(controllingSessionId)) + { + var controllingSession = GetSession(controllingSessionId); + AssertCanControl(session, controllingSession); + if (!controllingSession.UserId.IsEmpty()) + { + command.ControllingUserId = controllingSession.UserId.ToString("N", CultureInfo.InvariantCulture); + } + } + + return SendMessageToSession(session, SessionMessageType.Playstate, command, cancellationToken); + } + + private static void AssertCanControl(SessionInfo session, SessionInfo controllingSession) + { + ArgumentNullException.ThrowIfNull(session); + + ArgumentNullException.ThrowIfNull(controllingSession); + } + + /// <summary> + /// Sends the restart required message. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task SendRestartRequiredNotification(CancellationToken cancellationToken) + { + CheckDisposed(); + + return SendMessageToSessions(Sessions, SessionMessageType.RestartRequired, string.Empty, cancellationToken); + } + + /// <summary> + /// Adds the additional user. + /// </summary> + /// <param name="sessionId">The session identifier.</param> + /// <param name="userId">The user identifier.</param> + /// <exception cref="UnauthorizedAccessException">Cannot modify additional users without authenticating first.</exception> + /// <exception cref="ArgumentException">The requested user is already the primary user of the session.</exception> + public void AddAdditionalUser(string sessionId, Guid userId) + { + CheckDisposed(); + + var session = GetSession(sessionId); + + if (session.UserId.Equals(userId)) + { + throw new ArgumentException("The requested user is already the primary user of the session."); + } + + if (session.AdditionalUsers.All(i => !i.UserId.Equals(userId))) + { + var user = _userManager.GetUserById(userId); + var newUser = new SessionUserInfo + { + UserId = userId, + UserName = user.Username + }; + + session.AdditionalUsers = [.. session.AdditionalUsers, newUser]; + } + } + + /// <summary> + /// Removes the additional user. + /// </summary> + /// <param name="sessionId">The session identifier.</param> + /// <param name="userId">The user identifier.</param> + /// <exception cref="UnauthorizedAccessException">Cannot modify additional users without authenticating first.</exception> + /// <exception cref="ArgumentException">The requested user is already the primary user of the session.</exception> + public void RemoveAdditionalUser(string sessionId, Guid userId) + { + CheckDisposed(); + + var session = GetSession(sessionId); + + if (session.UserId.Equals(userId)) + { + throw new ArgumentException("The requested user is already the primary user of the session."); + } + + var user = session.AdditionalUsers.FirstOrDefault(i => i.UserId.Equals(userId)); + + if (user is not null) + { + var list = session.AdditionalUsers.ToList(); + list.Remove(user); + + session.AdditionalUsers = list.ToArray(); + } + } + + /// <summary> + /// Authenticates the new session. + /// </summary> + /// <param name="request">The authenticationrequest.</param> + /// <returns>The authentication result.</returns> + public Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request) + { + return AuthenticateNewSessionInternal(request, true); + } + + /// <summary> + /// Directly authenticates the session without enforcing password. + /// </summary> + /// <param name="request">The authentication request.</param> + /// <returns>The authentication result.</returns> + public Task<AuthenticationResult> AuthenticateDirect(AuthenticationRequest request) + { + return AuthenticateNewSessionInternal(request, false); + } + + internal async Task<AuthenticationResult> AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword) + { + CheckDisposed(); + + ArgumentException.ThrowIfNullOrEmpty(request.App); + ArgumentException.ThrowIfNullOrEmpty(request.DeviceId); + ArgumentException.ThrowIfNullOrEmpty(request.DeviceName); + ArgumentException.ThrowIfNullOrEmpty(request.AppVersion); + + User user = null; + if (!request.UserId.IsEmpty()) + { + user = _userManager.GetUserById(request.UserId); + } + + user ??= _userManager.GetUserByName(request.Username); + + if (enforcePassword) + { + user = await _userManager.AuthenticateUser( + request.Username, + request.Password, + request.RemoteEndPoint, + true).ConfigureAwait(false); + } + + if (user is null) + { + await _eventManager.PublishAsync(new AuthenticationRequestEventArgs(request)).ConfigureAwait(false); + throw new AuthenticationException("Invalid username or password entered."); + } + + if (!string.IsNullOrEmpty(request.DeviceId) + && !_deviceManager.CanAccessDevice(user, request.DeviceId)) + { + throw new SecurityException("User is not allowed access from this device."); + } + + int sessionsCount = Sessions.Count(i => i.UserId.Equals(user.Id)); + int maxActiveSessions = user.MaxActiveSessions; + _logger.LogInformation("Current/Max sessions for user {User}: {Sessions}/{Max}", user.Username, sessionsCount, maxActiveSessions); + if (maxActiveSessions >= 1 && sessionsCount >= maxActiveSessions) + { + throw new SecurityException("User is at their maximum number of sessions."); + } + + var token = await GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName).ConfigureAwait(false); + + var session = await LogSessionActivity( + request.App, + request.AppVersion, + request.DeviceId, + request.DeviceName, + request.RemoteEndPoint, + user).ConfigureAwait(false); + + var returnResult = new AuthenticationResult + { + User = _userManager.GetUserDto(user, request.RemoteEndPoint), + SessionInfo = ToSessionInfoDto(session), + AccessToken = token, + ServerId = _appHost.SystemId + }; + + await _eventManager.PublishAsync(new AuthenticationResultEventArgs(returnResult)).ConfigureAwait(false); + return returnResult; + } + + internal async Task<string> GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName) + { + // This should be validated above, but if it isn't don't delete all tokens. + ArgumentException.ThrowIfNullOrEmpty(deviceId); + + var existing = _deviceManager.GetDevices( + new DeviceQuery + { + DeviceId = deviceId, + UserId = user.Id + }).Items; + + foreach (var auth in existing) + { + try + { + // Logout any existing sessions for the user on this device + await Logout(auth).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while logging out existing session."); + } + } + + _logger.LogInformation("Creating new access token for user {0}", user.Id); + var device = await _deviceManager.CreateDevice(new Device(user.Id, app, appVersion, deviceName, deviceId)).ConfigureAwait(false); + + return device.AccessToken; + } + + /// <inheritdoc /> + public async Task Logout(string accessToken) + { + CheckDisposed(); + + ArgumentException.ThrowIfNullOrEmpty(accessToken); + + var existing = _deviceManager.GetDevices( + new DeviceQuery + { + Limit = 1, + AccessToken = accessToken + }).Items; + + if (existing.Count > 0) + { + await Logout(existing[0]).ConfigureAwait(false); + } + } + + /// <inheritdoc /> + public async Task Logout(Device device) + { + CheckDisposed(); + + _logger.LogInformation("Logging out access token {0}", device.AccessToken); + + await _deviceManager.DeleteDevice(device).ConfigureAwait(false); + + var sessions = Sessions + .Where(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + foreach (var session in sessions) + { + try + { + await ReportSessionEnded(session.Id).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error reporting session ended"); + } + } + } + + /// <inheritdoc /> + public async Task RevokeUserTokens(Guid userId, string currentAccessToken) + { + CheckDisposed(); + + var existing = _deviceManager.GetDevices(new DeviceQuery + { + UserId = userId + }); + + foreach (var info in existing.Items) + { + if (!string.Equals(currentAccessToken, info.AccessToken, StringComparison.OrdinalIgnoreCase)) + { + await Logout(info).ConfigureAwait(false); + } + } + } + + /// <summary> + /// Reports the capabilities. + /// </summary> + /// <param name="sessionId">The session identifier.</param> + /// <param name="capabilities">The capabilities.</param> + public void ReportCapabilities(string sessionId, ClientCapabilities capabilities) + { + CheckDisposed(); + + var session = GetSession(sessionId); + + ReportCapabilities(session, capabilities, true); + } + + private void ReportCapabilities( + SessionInfo session, + ClientCapabilities capabilities, + bool saveCapabilities) + { + session.Capabilities = capabilities; + + if (saveCapabilities) + { + CapabilitiesChanged?.Invoke( + this, + new SessionEventArgs + { + SessionInfo = session + }); + + _deviceManager.SaveCapabilities(session.DeviceId, capabilities); + } + } + + /// <summary> + /// Converts a BaseItem to a BaseItemInfo. + /// </summary> + private BaseItemDto GetItemInfo(BaseItem item, MediaSourceInfo mediaSource) + { + ArgumentNullException.ThrowIfNull(item); + + var dtoOptions = _itemInfoDtoOptions; + + if (_itemInfoDtoOptions is null) + { + dtoOptions = new DtoOptions + { + AddProgramRecordingInfo = false + }; + + var fields = dtoOptions.Fields.ToList(); + + fields.Remove(ItemFields.CanDelete); + fields.Remove(ItemFields.CanDownload); + fields.Remove(ItemFields.ChildCount); + fields.Remove(ItemFields.CustomRating); + fields.Remove(ItemFields.DateLastMediaAdded); + fields.Remove(ItemFields.DateLastRefreshed); + fields.Remove(ItemFields.DateLastSaved); + fields.Remove(ItemFields.DisplayPreferencesId); + fields.Remove(ItemFields.Etag); + fields.Remove(ItemFields.ItemCounts); + fields.Remove(ItemFields.MediaSourceCount); + fields.Remove(ItemFields.MediaStreams); + fields.Remove(ItemFields.MediaSources); + fields.Remove(ItemFields.People); + fields.Remove(ItemFields.PlayAccess); + fields.Remove(ItemFields.People); + fields.Remove(ItemFields.ProductionLocations); + fields.Remove(ItemFields.RecursiveItemCount); + fields.Remove(ItemFields.RemoteTrailers); + fields.Remove(ItemFields.SeasonUserData); + fields.Remove(ItemFields.Settings); + fields.Remove(ItemFields.SortName); + fields.Remove(ItemFields.Tags); + fields.Remove(ItemFields.ExtraIds); + + dtoOptions.Fields = fields.ToArray(); + + _itemInfoDtoOptions = dtoOptions; + } + + var info = _dtoService.GetBaseItemDto(item, dtoOptions); + + if (mediaSource is not null) + { + info.MediaStreams = mediaSource.MediaStreams.ToArray(); + } + + return info; + } + + private string GetImageCacheTag(User user) + { + try + { + return _imageProcessor.GetImageCacheTag(user); + } + catch (Exception e) + { + _logger.LogError(e, "Error getting image information for profile image"); + return null; + } + } + + /// <inheritdoc /> + public void ReportNowViewingItem(string sessionId, string itemId) + { + ArgumentException.ThrowIfNullOrEmpty(itemId); + + var item = _libraryManager.GetItemById(new Guid(itemId)); + var session = GetSession(sessionId); + + session.NowViewingItem = GetItemInfo(item, null); + } + + /// <inheritdoc /> + public void ReportTranscodingInfo(string deviceId, TranscodingInfo info) + { + var session = Sessions.FirstOrDefault(i => + string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); + + if (session is not null) + { + session.TranscodingInfo = info; + } + } + + /// <inheritdoc /> + public void ClearTranscodingInfo(string deviceId) + { + ReportTranscodingInfo(deviceId, null); + } + + /// <inheritdoc /> + public SessionInfo GetSession(string deviceId, string client, string version) + { + return Sessions.FirstOrDefault(i => + string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) + && string.Equals(i.Client, client, StringComparison.OrdinalIgnoreCase)); + } + + /// <inheritdoc /> + public Task<SessionInfo> GetSessionByAuthenticationToken(Device info, string deviceId, string remoteEndpoint, string appVersion) + { + ArgumentNullException.ThrowIfNull(info); + + var user = info.UserId.IsEmpty() + ? null + : _userManager.GetUserById(info.UserId); + + appVersion = string.IsNullOrEmpty(appVersion) + ? info.AppVersion + : appVersion; + + var deviceName = info.DeviceName; + var appName = info.AppName; + + if (string.IsNullOrEmpty(deviceId)) + { + deviceId = info.DeviceId; + } + + // Prevent argument exception + if (string.IsNullOrEmpty(appVersion)) + { + appVersion = "1"; + } + + return LogSessionActivity(appName, appVersion, deviceId, deviceName, remoteEndpoint, user); + } + + /// <inheritdoc /> + public async Task<SessionInfo> GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint) + { + var items = _deviceManager.GetDevices(new DeviceQuery + { + AccessToken = token, + Limit = 1 + }).Items; + + if (items.Count == 0) + { + return null; + } + + return await GetSessionByAuthenticationToken(items[0], deviceId, remoteEndpoint, null).ConfigureAwait(false); + } + + /// <inheritdoc/> + public IReadOnlyList<SessionInfoDto> GetSessions( + Guid userId, + string deviceId, + int? activeWithinSeconds, + Guid? controllableUserToCheck, + bool isApiKey) + { + var result = Sessions; + if (!string.IsNullOrEmpty(deviceId)) + { + result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); + } + + var userCanControlOthers = false; + var userIsAdmin = false; + User user = null; + + if (isApiKey) + { + userCanControlOthers = true; + userIsAdmin = true; + } + else if (!userId.IsEmpty()) + { + user = _userManager.GetUserById(userId); + if (user is not null) + { + userCanControlOthers = user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers); + userIsAdmin = user.HasPermission(PermissionKind.IsAdministrator); + } + else + { + return []; + } + } + + if (!controllableUserToCheck.IsNullOrEmpty()) + { + result = result.Where(i => i.SupportsRemoteControl); + + var controlledUser = _userManager.GetUserById(controllableUserToCheck.Value); + if (controlledUser is null) + { + return []; + } + + if (!controlledUser.HasPermission(PermissionKind.EnableSharedDeviceControl)) + { + // Controlled user has device sharing disabled + result = result.Where(i => !i.UserId.IsEmpty()); + } + + if (!userCanControlOthers) + { + // User cannot control other user's sessions, validate user id. + result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(userId)); + } + + result = result.Where(i => + { + if (isApiKey) + { + return true; + } + + if (user is null) + { + return false; + } + + return string.IsNullOrWhiteSpace(i.DeviceId) || _deviceManager.CanAccessDevice(user, i.DeviceId); + }); + } + else if (!userIsAdmin) + { + // Request isn't from administrator, limit to "own" sessions. + result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(userId)); + } + + if (!userIsAdmin) + { + // Don't report acceleration type for non-admin users. + result = result.Select(r => + { + if (r.TranscodingInfo is not null) + { + r.TranscodingInfo.HardwareAccelerationType = HardwareAccelerationType.none; + } + + return r; + }); + } + + if (activeWithinSeconds.HasValue && activeWithinSeconds.Value > 0) + { + var minActiveDate = DateTime.UtcNow.AddSeconds(0 - activeWithinSeconds.Value); + result = result.Where(i => i.LastActivityDate >= minActiveDate); + } + + return result.Select(ToSessionInfoDto).ToList(); + } + + /// <inheritdoc /> + public Task SendMessageToAdminSessions<T>(SessionMessageType name, T data, CancellationToken cancellationToken) + { + CheckDisposed(); + + var adminUserIds = _userManager.Users + .Where(i => i.HasPermission(PermissionKind.IsAdministrator)) + .Select(i => i.Id) + .ToList(); + + return SendMessageToUserSessions(adminUserIds, name, data, cancellationToken); + } + + /// <inheritdoc /> + public Task SendMessageToUserSessions<T>(List<Guid> userIds, SessionMessageType name, Func<T> dataFn, CancellationToken cancellationToken) + { + CheckDisposed(); + + var sessions = Sessions.Where(i => userIds.Any(i.ContainsUser)).ToList(); + + if (sessions.Count == 0) + { + return Task.CompletedTask; + } + + return SendMessageToSessions(sessions, name, dataFn(), cancellationToken); + } + + /// <inheritdoc /> + public Task SendMessageToUserSessions<T>(List<Guid> userIds, SessionMessageType name, T data, CancellationToken cancellationToken) + { + CheckDisposed(); + + var sessions = Sessions.Where(i => userIds.Any(i.ContainsUser)); + return SendMessageToSessions(sessions, name, data, cancellationToken); + } + + /// <inheritdoc /> + public Task SendMessageToUserDeviceSessions<T>(string deviceId, SessionMessageType name, T data, CancellationToken cancellationToken) + { + CheckDisposed(); + + var sessions = Sessions.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); + + return SendMessageToSessions(sessions, name, data, cancellationToken); + } + + /// <inheritdoc /> + public async ValueTask DisposeAsync() + { + if (_disposed) + { + return; + } + + foreach (var session in _activeConnections.Values) + { + await session.DisposeAsync().ConfigureAwait(false); + } + + if (_idleTimer is not null) + { + await _idleTimer.DisposeAsync().ConfigureAwait(false); + _idleTimer = null; + } + + if (_inactiveTimer is not null) + { + await _inactiveTimer.DisposeAsync().ConfigureAwait(false); + _inactiveTimer = null; + } + + await _shutdownCallback.DisposeAsync().ConfigureAwait(false); + + _deviceManager.DeviceOptionsUpdated -= OnDeviceManagerDeviceOptionsUpdated; + _disposed = true; + } + + private async void OnApplicationStopping() + { + _logger.LogInformation("Sending shutdown notifications"); + try + { + var messageType = _appHost.ShouldRestart ? SessionMessageType.ServerRestarting : SessionMessageType.ServerShuttingDown; + + await SendMessageToSessions(Sessions, messageType, string.Empty, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error sending server shutdown notifications"); + } + + // Close open websockets to allow Kestrel to shut down cleanly + foreach (var session in _activeConnections.Values) + { + await session.DisposeAsync().ConfigureAwait(false); + } + + _activeConnections.Clear(); + _activeLiveStreamSessions.Clear(); + } + } +} diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs new file mode 100644 index 00000000..6a26e92e --- /dev/null +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -0,0 +1,268 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Net.WebSocketMessages.Outbound; +using MediaBrowser.Controller.Session; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Session +{ + /// <summary> + /// Class SessionWebSocketListener. + /// </summary> + public sealed class SessionWebSocketListener : IWebSocketListener, IDisposable + { + /// <summary> + /// The timeout in seconds after which a WebSocket is considered to be lost. + /// </summary> + private const int WebSocketLostTimeout = 60; + + /// <summary> + /// The keep-alive interval factor; controls how often the watcher will check on the status of the WebSockets. + /// </summary> + private const float IntervalFactor = 0.2f; + + /// <summary> + /// The ForceKeepAlive factor; controls when a ForceKeepAlive is sent. + /// </summary> + private const float ForceKeepAliveFactor = 0.75f; + + /// <summary> + /// The WebSocket watchlist. + /// </summary> + private readonly HashSet<IWebSocketConnection> _webSockets = new HashSet<IWebSocketConnection>(); + + /// <summary> + /// Lock used for accessing the WebSockets watchlist. + /// </summary> + private readonly Lock _webSocketsLock = new(); + + private readonly ISessionManager _sessionManager; + private readonly IUserManager _userManager; + private readonly ILogger<SessionWebSocketListener> _logger; + private readonly ILoggerFactory _loggerFactory; + + /// <summary> + /// The KeepAlive cancellation token. + /// </summary> + private System.Timers.Timer _keepAlive; + + /// <summary> + /// Initializes a new instance of the <see cref="SessionWebSocketListener" /> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="sessionManager">The session manager.</param> + /// <param name="userManager">The user manager.</param> + /// <param name="loggerFactory">The logger factory.</param> + public SessionWebSocketListener( + ILogger<SessionWebSocketListener> logger, + ISessionManager sessionManager, + IUserManager userManager, + ILoggerFactory loggerFactory) + { + _logger = logger; + _sessionManager = sessionManager; + _userManager = userManager; + _loggerFactory = loggerFactory; + _keepAlive = new System.Timers.Timer(TimeSpan.FromSeconds(WebSocketLostTimeout * IntervalFactor)) + { + AutoReset = true, + Enabled = false + }; + _keepAlive.Elapsed += KeepAliveSockets; + } + + /// <inheritdoc /> + public void Dispose() + { + if (_keepAlive is not null) + { + _keepAlive.Stop(); + _keepAlive.Elapsed -= KeepAliveSockets; + _keepAlive.Dispose(); + _keepAlive = null!; + } + + lock (_webSocketsLock) + { + foreach (var webSocket in _webSockets) + { + webSocket.Closed -= OnWebSocketClosed; + } + + _webSockets.Clear(); + } + } + + /// <summary> + /// Processes the message. + /// </summary> + /// <param name="message">The message.</param> + /// <returns>Task.</returns> + public Task ProcessMessageAsync(WebSocketMessageInfo message) + => Task.CompletedTask; + + /// <inheritdoc /> + public async Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection, HttpContext httpContext) + { + var session = await RequestHelpers.GetSession(_sessionManager, _userManager, httpContext).ConfigureAwait(false); + EnsureController(session, connection); + await KeepAliveWebSocket(connection).ConfigureAwait(false); + } + + private void EnsureController(SessionInfo session, IWebSocketConnection connection) + { + var controllerInfo = session.EnsureController<WebSocketController>( + s => new WebSocketController(_loggerFactory.CreateLogger<WebSocketController>(), s, _sessionManager)); + + var controller = (WebSocketController)controllerInfo.Item1; + controller.AddWebSocket(connection); + + _sessionManager.OnSessionControllerConnected(session); + } + + /// <summary> + /// Called when a WebSocket is closed. + /// </summary> + /// <param name="sender">The WebSocket.</param> + /// <param name="e">The event arguments.</param> + private void OnWebSocketClosed(object? sender, EventArgs e) + { + if (sender is null) + { + return; + } + + var webSocket = (IWebSocketConnection)sender; + _logger.LogDebug("WebSocket {0} is closed.", webSocket); + RemoveWebSocket(webSocket); + } + + /// <summary> + /// Adds a WebSocket to the KeepAlive watchlist. + /// </summary> + /// <param name="webSocket">The WebSocket to monitor.</param> + private async Task KeepAliveWebSocket(IWebSocketConnection webSocket) + { + lock (_webSocketsLock) + { + if (!_webSockets.Add(webSocket)) + { + _logger.LogWarning("Multiple attempts to keep alive single WebSocket {0}", webSocket); + return; + } + + webSocket.Closed += OnWebSocketClosed; + webSocket.LastKeepAliveDate = DateTime.UtcNow; + + _keepAlive.Start(); + } + + // Notify WebSocket about timeout + try + { + await SendForceKeepAlive(webSocket).ConfigureAwait(false); + } + catch (WebSocketException exception) + { + _logger.LogWarning(exception, "Cannot send ForceKeepAlive message to WebSocket {0}.", webSocket); + } + } + + /// <summary> + /// Removes a WebSocket from the KeepAlive watchlist. + /// </summary> + /// <param name="webSocket">The WebSocket to remove.</param> + private void RemoveWebSocket(IWebSocketConnection webSocket) + { + lock (_webSocketsLock) + { + if (_webSockets.Remove(webSocket)) + { + webSocket.Closed -= OnWebSocketClosed; + } + else + { + _logger.LogWarning("WebSocket {0} not on watchlist.", webSocket); + } + + if (_webSockets.Count == 0) + { + _keepAlive.Stop(); + } + } + } + + /// <summary> + /// Checks status of KeepAlive of WebSockets. + /// </summary> + private async void KeepAliveSockets(object? o, EventArgs? e) + { + List<IWebSocketConnection> inactive; + List<IWebSocketConnection> lost; + + lock (_webSocketsLock) + { + _logger.LogDebug("Watching {0} WebSockets.", _webSockets.Count); + + inactive = _webSockets.Where(i => + { + var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; + return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); + }).ToList(); + lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout).ToList(); + } + + if (inactive.Count > 0) + { + _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count); + } + + foreach (var webSocket in inactive) + { + try + { + await SendForceKeepAlive(webSocket).ConfigureAwait(false); + } + catch (WebSocketException exception) + { + _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); + lost.Add(webSocket); + } + } + + lock (_webSocketsLock) + { + if (lost.Count > 0) + { + _logger.LogInformation("Lost {0} WebSockets.", lost.Count); + foreach (var webSocket in lost) + { + // TODO: handle session relative to the lost webSocket + RemoveWebSocket(webSocket); + } + } + } + } + + /// <summary> + /// Sends a ForceKeepAlive message to a WebSocket. + /// </summary> + /// <param name="webSocket">The WebSocket.</param> + /// <returns>Task.</returns> + private async Task SendForceKeepAlive(IWebSocketConnection webSocket) + { + await webSocket.SendAsync( + new ForceKeepAliveMessage(WebSocketLostTimeout), + CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs new file mode 100644 index 00000000..c45a4a60 --- /dev/null +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -0,0 +1,186 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Net.WebSocketMessages; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Session +{ + public sealed class WebSocketController : ISessionController, IAsyncDisposable, IDisposable + { + private readonly ILogger<WebSocketController> _logger; + private readonly ISessionManager _sessionManager; + private readonly SessionInfo _session; + + private readonly List<IWebSocketConnection> _sockets; + private readonly ReaderWriterLockSlim _socketsLock; + private bool _disposed = false; + + public WebSocketController( + ILogger<WebSocketController> logger, + SessionInfo session, + ISessionManager sessionManager) + { + _logger = logger; + _session = session; + _sessionManager = sessionManager; + _sockets = new(); + _socketsLock = new(); + } + + private bool HasOpenSockets + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + try + { + _socketsLock.EnterReadLock(); + return _sockets.Any(i => i.State == WebSocketState.Open); + } + finally + { + _socketsLock.ExitReadLock(); + } + } + } + + /// <inheritdoc /> + public bool SupportsMediaControl => HasOpenSockets; + + /// <inheritdoc /> + public bool IsSessionActive => HasOpenSockets; + + public void AddWebSocket(IWebSocketConnection connection) + { + _logger.LogDebug("Adding websocket to session {Session}", _session.Id); + ObjectDisposedException.ThrowIf(_disposed, this); + try + { + _socketsLock.EnterWriteLock(); + _sockets.Add(connection); + connection.Closed += OnConnectionClosed; + } + finally + { + _socketsLock.ExitWriteLock(); + } + } + + private async void OnConnectionClosed(object? sender, EventArgs e) + { + var connection = sender as IWebSocketConnection ?? throw new ArgumentException($"{nameof(sender)} is not of type {nameof(IWebSocketConnection)}", nameof(sender)); + _logger.LogDebug("Removing websocket from session {Session}", _session.Id); + ObjectDisposedException.ThrowIf(_disposed, this); + try + { + _socketsLock.EnterWriteLock(); + _sockets.Remove(connection); + connection.Closed -= OnConnectionClosed; + } + finally + { + _socketsLock.ExitWriteLock(); + } + + await _sessionManager.CloseIfNeededAsync(_session).ConfigureAwait(false); + } + + /// <inheritdoc /> + public Task SendMessage<T>( + SessionMessageType name, + Guid messageId, + T data, + CancellationToken cancellationToken) + { + ObjectDisposedException.ThrowIf(_disposed, this); + IWebSocketConnection? socket; + try + { + _socketsLock.EnterReadLock(); + socket = _sockets.Where(i => i.State == WebSocketState.Open).MaxBy(i => i.LastActivityDate); + } + finally + { + _socketsLock.ExitReadLock(); + } + + if (socket is null) + { + return Task.CompletedTask; + } + + return socket.SendAsync( + new OutboundWebSocketMessage<T> + { + Data = data, + MessageType = name, + MessageId = messageId + }, + cancellationToken); + } + + /// <inheritdoc /> + public void Dispose() + { + if (_disposed) + { + return; + } + + try + { + _socketsLock.EnterWriteLock(); + foreach (var socket in _sockets) + { + socket.Closed -= OnConnectionClosed; + socket.Dispose(); + } + + _sockets.Clear(); + } + finally + { + _socketsLock.ExitWriteLock(); + } + + _socketsLock.Dispose(); + _disposed = true; + } + + public async ValueTask DisposeAsync() + { + if (_disposed) + { + return; + } + + try + { + _socketsLock.EnterWriteLock(); + foreach (var socket in _sockets) + { + socket.Closed -= OnConnectionClosed; + await socket.DisposeAsync().ConfigureAwait(false); + } + + _sockets.Clear(); + } + finally + { + _socketsLock.ExitWriteLock(); + } + + _socketsLock.Dispose(); + _disposed = true; + } + } +} diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs new file mode 100644 index 00000000..6d13c6d5 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -0,0 +1,160 @@ +#pragma warning disable CS1591 + +using System; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + public class AiredEpisodeOrderComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.AiredEpisodeOrder; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + ArgumentNullException.ThrowIfNull(x); + + ArgumentNullException.ThrowIfNull(y); + + var episode1 = x as Episode; + var episode2 = y as Episode; + + if (episode1 is null) + { + if (episode2 is null) + { + return 0; + } + + return 1; + } + + if (episode2 is null) + { + return -1; + } + + return Compare(episode1, episode2); + } + + private int Compare(Episode x, Episode y) + { + var isXSpecial = (x.ParentIndexNumber ?? -1) == 0; + var isYSpecial = (y.ParentIndexNumber ?? -1) == 0; + + if (isXSpecial && isYSpecial) + { + return CompareSpecials(x, y); + } + + if (!isXSpecial && !isYSpecial) + { + return CompareEpisodes(x, y); + } + + if (!isXSpecial) + { + return CompareEpisodeToSpecial(x, y); + } + + return CompareEpisodeToSpecial(y, x) * -1; + } + + private static int CompareEpisodeToSpecial(Episode x, Episode y) + { + // http://thetvdb.com/wiki/index.php?title=Special_Episodes + + var xSeason = x.ParentIndexNumber ?? -1; + var ySeason = y.AirsAfterSeasonNumber ?? y.AirsBeforeSeasonNumber ?? -1; + + if (xSeason != ySeason) + { + return xSeason.CompareTo(ySeason); + } + + // Special comes after episode + if (y.AirsAfterSeasonNumber.HasValue) + { + return -1; + } + + var yEpisode = y.AirsBeforeEpisodeNumber; + + // Special comes before the season + if (!yEpisode.HasValue) + { + return 1; + } + + // Compare episode number + var xEpisode = x.IndexNumber; + + if (!xEpisode.HasValue) + { + // Can't really compare if this happens + return 0; + } + + // Special comes before episode + if (xEpisode.Value == yEpisode.Value) + { + return 1; + } + + return xEpisode.Value.CompareTo(yEpisode.Value); + } + + private int CompareSpecials(Episode x, Episode y) + { + return GetSpecialCompareValue(x).CompareTo(GetSpecialCompareValue(y)); + } + + private static long GetSpecialCompareValue(Episode item) + { + // First sort by season number + // Since there are three sort orders, pad with 9 digits (3 for each, figure 1000 episode buffer should be enough) + var val = (item.AirsAfterSeasonNumber ?? item.AirsBeforeSeasonNumber ?? 0) * 1000000000L; + + // Second sort order is if it airs after the season + if (item.AirsAfterSeasonNumber.HasValue) + { + val += 1000000; + } + + // Third level is the episode number + val += (item.AirsBeforeEpisodeNumber ?? 0) * 1000; + + // Finally, if that's still the same, last resort is the special number itself + val += item.IndexNumber ?? 0; + + return val; + } + + private static int CompareEpisodes(Episode x, Episode y) + { + var xValue = ((x.ParentIndexNumber ?? -1) * 1000) + (x.IndexNumber ?? -1); + var yValue = ((y.ParentIndexNumber ?? -1) * 1000) + (y.IndexNumber ?? -1); + var comparisonResult = xValue.CompareTo(yValue); + // If equal, compare premiere dates + if (comparisonResult == 0 && x.PremiereDate.HasValue && y.PremiereDate.HasValue) + { + comparisonResult = DateTime.Compare(x.PremiereDate.Value, y.PremiereDate.Value); + } + + return comparisonResult; + } + } +} diff --git a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs new file mode 100644 index 00000000..59185cdb --- /dev/null +++ b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs @@ -0,0 +1,42 @@ +using System; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + /// <summary> + /// Allows comparing artists of albums. Only the first artist of each album is considered. + /// </summary> + public class AlbumArtistComparer : IBaseItemComparer + { + /// <summary> + /// Gets the item type this comparer compares. + /// </summary> + public ItemSortBy Type => ItemSortBy.AlbumArtist; + + /// <summary> + /// Compares the specified arguments on their primary artist. + /// </summary> + /// <param name="x">First item to compare.</param> + /// <param name="y">Second item to compare.</param> + /// <returns>Zero if equal, else negative or positive number to indicate order.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + return string.Compare(GetFirstAlbumArtist(x), GetFirstAlbumArtist(y), StringComparison.OrdinalIgnoreCase); + } + + private static string? GetFirstAlbumArtist(BaseItem? x) + { + if (x is IHasAlbumArtist audio + && audio.AlbumArtists.Count != 0) + { + return audio.AlbumArtists[0]; + } + + return null; + } + } +} diff --git a/Emby.Server.Implementations/Sorting/AlbumComparer.cs b/Emby.Server.Implementations/Sorting/AlbumComparer.cs new file mode 100644 index 00000000..e0711365 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/AlbumComparer.cs @@ -0,0 +1,42 @@ +using System; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + /// <summary> + /// Class AlbumComparer. + /// </summary> + public class AlbumComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.Album; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + return string.Compare(GetValue(x), GetValue(y), StringComparison.OrdinalIgnoreCase); + } + + /// <summary> + /// Gets the value. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>System.String.</returns> + private static string GetValue(BaseItem? x) + { + return x is Audio audio ? audio.Album : string.Empty; + } + } +} diff --git a/Emby.Server.Implementations/Sorting/ArtistComparer.cs b/Emby.Server.Implementations/Sorting/ArtistComparer.cs new file mode 100644 index 00000000..f99977e5 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/ArtistComparer.cs @@ -0,0 +1,39 @@ +using System; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + /// <summary> + /// Class ArtistComparer. + /// </summary> + public class ArtistComparer : IBaseItemComparer + { + /// <inheritdoc /> + public ItemSortBy Type => ItemSortBy.Artist; + + /// <inheritdoc /> + public int Compare(BaseItem? x, BaseItem? y) + { + return string.Compare(GetValue(x), GetValue(y), StringComparison.OrdinalIgnoreCase); + } + + /// <summary> + /// Gets the value. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>System.String.</returns> + private static string? GetValue(BaseItem? x) + { + if (x is not Audio audio) + { + return string.Empty; + } + + return audio.Artists.Count == 0 ? null : audio.Artists[0]; + } + } +} diff --git a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs new file mode 100644 index 00000000..9e02ea2a --- /dev/null +++ b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs @@ -0,0 +1,34 @@ +#pragma warning disable CS1591 + +using System; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + public class CommunityRatingComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.CommunityRating; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + ArgumentNullException.ThrowIfNull(x); + + ArgumentNullException.ThrowIfNull(y); + + return (x.CommunityRating ?? 0).CompareTo(y.CommunityRating ?? 0); + } + } +} diff --git a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs new file mode 100644 index 00000000..d4a8d468 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs @@ -0,0 +1,35 @@ +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + /// <summary> + /// Class CriticRatingComparer. + /// </summary> + public class CriticRatingComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.CriticRating; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + return GetValue(x).CompareTo(GetValue(y)); + } + + private static float GetValue(BaseItem? x) + { + return x?.CriticRating ?? 0; + } + } +} diff --git a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs new file mode 100644 index 00000000..b86b4432 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs @@ -0,0 +1,35 @@ +using System; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + /// <summary> + /// Class DateCreatedComparer. + /// </summary> + public class DateCreatedComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.DateCreated; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + ArgumentNullException.ThrowIfNull(x); + + ArgumentNullException.ThrowIfNull(y); + + return DateTime.Compare(x.DateCreated, y.DateCreated); + } + } +} diff --git a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs new file mode 100644 index 00000000..f10e7fcb --- /dev/null +++ b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs @@ -0,0 +1,69 @@ +#nullable disable +#pragma warning disable CS1591 + +using System; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + public class DateLastMediaAddedComparer : IUserBaseItemComparer + { + /// <summary> + /// Gets or sets the user. + /// </summary> + /// <value>The user.</value> + public User User { get; set; } + + /// <summary> + /// Gets or sets the user manager. + /// </summary> + /// <value>The user manager.</value> + public IUserManager UserManager { get; set; } + + /// <summary> + /// Gets or sets the user data manager. + /// </summary> + /// <value>The user data manager.</value> + public IUserDataManager UserDataManager { get; set; } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.DateLastContentAdded; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return GetDate(x).CompareTo(GetDate(y)); + } + + /// <summary> + /// Gets the date. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>DateTime.</returns> + private static DateTime GetDate(BaseItem x) + { + if (x is Folder folder) + { + if (folder.DateLastMediaAdded.HasValue) + { + return folder.DateLastMediaAdded.Value; + } + } + + return DateTime.MinValue; + } + } +} diff --git a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs new file mode 100644 index 00000000..2c8e2b37 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs @@ -0,0 +1,70 @@ +#nullable disable + +using System; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + /// <summary> + /// Class DatePlayedComparer. + /// </summary> + public class DatePlayedComparer : IUserBaseItemComparer + { + /// <summary> + /// Gets or sets the user. + /// </summary> + /// <value>The user.</value> + public User User { get; set; } + + /// <summary> + /// Gets or sets the user manager. + /// </summary> + /// <value>The user manager.</value> + public IUserManager UserManager { get; set; } + + /// <summary> + /// Gets or sets the user data manager. + /// </summary> + /// <value>The user data manager.</value> + public IUserDataManager UserDataManager { get; set; } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.DatePlayed; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return GetDate(x).CompareTo(GetDate(y)); + } + + /// <summary> + /// Gets the date. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>DateTime.</returns> + private DateTime GetDate(BaseItem x) + { + var userdata = UserDataManager.GetUserData(User, x); + + if (userdata is not null && userdata.LastPlayedDate.HasValue) + { + return userdata.LastPlayedDate.Value; + } + + return DateTime.MinValue; + } + } +} diff --git a/Emby.Server.Implementations/Sorting/IndexNumberComparer.cs b/Emby.Server.Implementations/Sorting/IndexNumberComparer.cs new file mode 100644 index 00000000..11cad625 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/IndexNumberComparer.cs @@ -0,0 +1,50 @@ +using System; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + /// <summary> + /// Class IndexNumberComparer. + /// </summary> + public class IndexNumberComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.IndexNumber; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + ArgumentNullException.ThrowIfNull(x); + + ArgumentNullException.ThrowIfNull(y); + + if (!x.IndexNumber.HasValue && !y.IndexNumber.HasValue) + { + return 0; + } + + if (!x.IndexNumber.HasValue) + { + return -1; + } + + if (!y.IndexNumber.HasValue) + { + return 1; + } + + return x.IndexNumber.Value.CompareTo(y.IndexNumber.Value); + } + } +} diff --git a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs new file mode 100644 index 00000000..86d08ed2 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs @@ -0,0 +1,59 @@ +#nullable disable +#pragma warning disable CS1591 + +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Sorting; + +namespace Emby.Server.Implementations.Sorting +{ + public class IsFavoriteOrLikeComparer : IUserBaseItemComparer + { + /// <summary> + /// Gets or sets the user. + /// </summary> + /// <value>The user.</value> + public User User { get; set; } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.IsFavoriteOrLiked; + + /// <summary> + /// Gets or sets the user data manager. + /// </summary> + /// <value>The user data manager.</value> + public IUserDataManager UserDataManager { get; set; } + + /// <summary> + /// Gets or sets the user manager. + /// </summary> + /// <value>The user manager.</value> + public IUserManager UserManager { get; set; } + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return GetValue(x).CompareTo(GetValue(y)); + } + + /// <summary> + /// Gets the date. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>DateTime.</returns> + private int GetValue(BaseItem x) + { + return x.IsFavoriteOrLiked(User, userItemData: null) ? 0 : 1; + } + } +} diff --git a/Emby.Server.Implementations/Sorting/IsFolderComparer.cs b/Emby.Server.Implementations/Sorting/IsFolderComparer.cs new file mode 100644 index 00000000..6f0ca59c --- /dev/null +++ b/Emby.Server.Implementations/Sorting/IsFolderComparer.cs @@ -0,0 +1,39 @@ +#pragma warning disable CS1591 + +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + public class IsFolderComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.IsFolder; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + return GetValue(x).CompareTo(GetValue(y)); + } + + /// <summary> + /// Gets the value. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>System.String.</returns> + private static int GetValue(BaseItem? x) + { + return x?.IsFolder ?? true ? 0 : 1; + } + } +} diff --git a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs new file mode 100644 index 00000000..9faa02f1 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs @@ -0,0 +1,60 @@ +#nullable disable + +#pragma warning disable CS1591 + +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Sorting; + +namespace Emby.Server.Implementations.Sorting +{ + public class IsPlayedComparer : IUserBaseItemComparer + { + /// <summary> + /// Gets or sets the user. + /// </summary> + /// <value>The user.</value> + public User User { get; set; } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.IsUnplayed; + + /// <summary> + /// Gets or sets the user data manager. + /// </summary> + /// <value>The user data manager.</value> + public IUserDataManager UserDataManager { get; set; } + + /// <summary> + /// Gets or sets the user manager. + /// </summary> + /// <value>The user manager.</value> + public IUserManager UserManager { get; set; } + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return GetValue(x).CompareTo(GetValue(y)); + } + + /// <summary> + /// Gets the date. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>DateTime.</returns> + private int GetValue(BaseItem x) + { + return x.IsPlayed(User, userItemData: null) ? 0 : 1; + } + } +} diff --git a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs new file mode 100644 index 00000000..6f177c46 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs @@ -0,0 +1,60 @@ +#nullable disable + +#pragma warning disable CS1591 + +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Sorting; + +namespace Emby.Server.Implementations.Sorting +{ + public class IsUnplayedComparer : IUserBaseItemComparer + { + /// <summary> + /// Gets or sets the user. + /// </summary> + /// <value>The user.</value> + public User User { get; set; } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.IsUnplayed; + + /// <summary> + /// Gets or sets the user data manager. + /// </summary> + /// <value>The user data manager.</value> + public IUserDataManager UserDataManager { get; set; } + + /// <summary> + /// Gets or sets the user manager. + /// </summary> + /// <value>The user manager.</value> + public IUserManager UserManager { get; set; } + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return GetValue(x).CompareTo(GetValue(y)); + } + + /// <summary> + /// Gets the date. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>DateTime.</returns> + private int GetValue(BaseItem x) + { + return x.IsUnplayed(User, userItemData: null) ? 0 : 1; + } + } +} diff --git a/Emby.Server.Implementations/Sorting/NameComparer.cs b/Emby.Server.Implementations/Sorting/NameComparer.cs new file mode 100644 index 00000000..72d9c797 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/NameComparer.cs @@ -0,0 +1,35 @@ +using System; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + /// <summary> + /// Class NameComparer. + /// </summary> + public class NameComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.Name; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + ArgumentNullException.ThrowIfNull(x); + + ArgumentNullException.ThrowIfNull(y); + + return string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs new file mode 100644 index 00000000..789af01c --- /dev/null +++ b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs @@ -0,0 +1,54 @@ +using System; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; + +namespace Emby.Server.Implementations.Sorting; + +/// <summary> +/// Class providing comparison for official ratings. +/// </summary> +public class OfficialRatingComparer : IBaseItemComparer +{ + private readonly ILocalizationManager _localizationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="OfficialRatingComparer"/> class. + /// </summary> + /// <param name="localizationManager">Instance of the <see cref="ILocalizationManager"/> interface.</param> + public OfficialRatingComparer(ILocalizationManager localizationManager) + { + _localizationManager = localizationManager; + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.OfficialRating; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + ArgumentNullException.ThrowIfNull(x); + ArgumentNullException.ThrowIfNull(y); + var zeroRating = new ParentalRatingScore(0, 0); + + var ratingX = string.IsNullOrEmpty(x.OfficialRating) ? zeroRating : _localizationManager.GetRatingScore(x.OfficialRating) ?? zeroRating; + var ratingY = string.IsNullOrEmpty(y.OfficialRating) ? zeroRating : _localizationManager.GetRatingScore(y.OfficialRating) ?? zeroRating; + var scoreCompare = ratingX.Score.CompareTo(ratingY.Score); + if (scoreCompare is 0) + { + return (ratingX.SubScore ?? 0).CompareTo(ratingY.SubScore ?? 0); + } + + return scoreCompare; + } +} diff --git a/Emby.Server.Implementations/Sorting/ParentIndexNumberComparer.cs b/Emby.Server.Implementations/Sorting/ParentIndexNumberComparer.cs new file mode 100644 index 00000000..5aeac29b --- /dev/null +++ b/Emby.Server.Implementations/Sorting/ParentIndexNumberComparer.cs @@ -0,0 +1,50 @@ +using System; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + /// <summary> + /// Class ParentIndexNumberComparer. + /// </summary> + public class ParentIndexNumberComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.ParentIndexNumber; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + ArgumentNullException.ThrowIfNull(x); + + ArgumentNullException.ThrowIfNull(y); + + if (!x.ParentIndexNumber.HasValue && !y.ParentIndexNumber.HasValue) + { + return 0; + } + + if (!x.ParentIndexNumber.HasValue) + { + return -1; + } + + if (!y.ParentIndexNumber.HasValue) + { + return 1; + } + + return x.ParentIndexNumber.Value.CompareTo(y.ParentIndexNumber.Value); + } + } +} diff --git a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs new file mode 100644 index 00000000..26e28b03 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs @@ -0,0 +1,64 @@ +#nullable disable + +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + /// <summary> + /// Class PlayCountComparer. + /// </summary> + public class PlayCountComparer : IUserBaseItemComparer + { + /// <summary> + /// Gets or sets the user. + /// </summary> + /// <value>The user.</value> + public User User { get; set; } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.PlayCount; + + /// <summary> + /// Gets or sets the user data manager. + /// </summary> + /// <value>The user data manager.</value> + public IUserDataManager UserDataManager { get; set; } + + /// <summary> + /// Gets or sets the user manager. + /// </summary> + /// <value>The user manager.</value> + public IUserManager UserManager { get; set; } + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return GetValue(x).CompareTo(GetValue(y)); + } + + /// <summary> + /// Gets the date. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>DateTime.</returns> + private int GetValue(BaseItem x) + { + var userdata = UserDataManager.GetUserData(User, x); + + return userdata is null ? 0 : userdata.PlayCount; + } + } +} diff --git a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs new file mode 100644 index 00000000..8c8b8824 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs @@ -0,0 +1,63 @@ +using System; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + /// <summary> + /// Class PremiereDateComparer. + /// </summary> + public class PremiereDateComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.PremiereDate; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + return GetDate(x).CompareTo(GetDate(y)); + } + + /// <summary> + /// Gets the date. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>DateTime.</returns> + private static DateTime GetDate(BaseItem? x) + { + if (x is null) + { + return DateTime.MinValue; + } + + if (x.PremiereDate.HasValue) + { + return x.PremiereDate.Value; + } + + if (x.ProductionYear.HasValue) + { + try + { + return new DateTime(x.ProductionYear.Value, 1, 1, 0, 0, 0, DateTimeKind.Utc); + } + catch (ArgumentOutOfRangeException) + { + // Don't blow up if the item has a bad ProductionYear, just return MinValue + } + } + + return DateTime.MinValue; + } + } +} diff --git a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs new file mode 100644 index 00000000..9aec87f1 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs @@ -0,0 +1,55 @@ +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + /// <summary> + /// Class ProductionYearComparer. + /// </summary> + public class ProductionYearComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.ProductionYear; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + return GetValue(x).CompareTo(GetValue(y)); + } + + /// <summary> + /// Gets the date. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>DateTime.</returns> + private static int GetValue(BaseItem? x) + { + if (x is null) + { + return 0; + } + + if (x.ProductionYear.HasValue) + { + return x.ProductionYear.Value; + } + + if (x.PremiereDate.HasValue) + { + return x.PremiereDate.Value.Year; + } + + return 0; + } + } +} diff --git a/Emby.Server.Implementations/Sorting/RandomComparer.cs b/Emby.Server.Implementations/Sorting/RandomComparer.cs new file mode 100644 index 00000000..6f8ea5b7 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/RandomComparer.cs @@ -0,0 +1,31 @@ +using System; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + /// <summary> + /// Class RandomComparer. + /// </summary> + public class RandomComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.Random; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + return Guid.NewGuid().CompareTo(Guid.NewGuid()); + } + } +} diff --git a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs new file mode 100644 index 00000000..3c096ab0 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs @@ -0,0 +1,34 @@ +using System; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + /// <summary> + /// Class RuntimeComparer. + /// </summary> + public class RuntimeComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.Runtime; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + ArgumentNullException.ThrowIfNull(x); + ArgumentNullException.ThrowIfNull(y); + + return (x.RunTimeTicks ?? 0).CompareTo(y.RunTimeTicks ?? 0); + } + } +} diff --git a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs new file mode 100644 index 00000000..ed42fd6d --- /dev/null +++ b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs @@ -0,0 +1,36 @@ +#pragma warning disable CS1591 + +using System; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + public class SeriesSortNameComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.SeriesSortName; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + return string.Compare(GetValue(x), GetValue(y), StringComparison.OrdinalIgnoreCase); + } + + private static string? GetValue(BaseItem? item) + { + var hasSeries = item as IHasSeries; + return hasSeries?.FindSeriesSortName(); + } + } +} diff --git a/Emby.Server.Implementations/Sorting/SortNameComparer.cs b/Emby.Server.Implementations/Sorting/SortNameComparer.cs new file mode 100644 index 00000000..314c25d1 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/SortNameComparer.cs @@ -0,0 +1,34 @@ +using System; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + /// <summary> + /// Class SortNameComparer. + /// </summary> + public class SortNameComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.SortName; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + ArgumentNullException.ThrowIfNull(x); + ArgumentNullException.ThrowIfNull(y); + + return string.Compare(x.SortName, y.SortName, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/Emby.Server.Implementations/Sorting/StartDateComparer.cs b/Emby.Server.Implementations/Sorting/StartDateComparer.cs new file mode 100644 index 00000000..861ca2d3 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/StartDateComparer.cs @@ -0,0 +1,45 @@ +#pragma warning disable CS1591 + +using System; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Sorting; + +namespace Emby.Server.Implementations.Sorting +{ + public class StartDateComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.StartDate; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + return GetDate(x).CompareTo(GetDate(y)); + } + + /// <summary> + /// Gets the date. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>DateTime.</returns> + private static DateTime GetDate(BaseItem? x) + { + if (x is LiveTvProgram hasStartDate) + { + return hasStartDate.StartDate; + } + + return DateTime.MinValue; + } + } +} diff --git a/Emby.Server.Implementations/Sorting/StudioComparer.cs b/Emby.Server.Implementations/Sorting/StudioComparer.cs new file mode 100644 index 00000000..6d041cf1 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/StudioComparer.cs @@ -0,0 +1,34 @@ +#pragma warning disable CS1591 + +using System; +using System.Globalization; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; + +namespace Emby.Server.Implementations.Sorting +{ + public class StudioComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public ItemSortBy Type => ItemSortBy.Studio; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + ArgumentNullException.ThrowIfNull(x); + ArgumentNullException.ThrowIfNull(y); + + return CultureInfo.InvariantCulture.CompareInfo.Compare(x.Studios.FirstOrDefault(), y.Studios.FirstOrDefault(), CompareOptions.NumericOrdering); + } + } +} diff --git a/Emby.Server.Implementations/SyncPlay/Group.cs b/Emby.Server.Implementations/SyncPlay/Group.cs new file mode 100644 index 00000000..c2e834ad --- /dev/null +++ b/Emby.Server.Implementations/SyncPlay/Group.cs @@ -0,0 +1,679 @@ +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.SyncPlay; +using MediaBrowser.Controller.SyncPlay.GroupStates; +using MediaBrowser.Controller.SyncPlay.Queue; +using MediaBrowser.Controller.SyncPlay.Requests; +using MediaBrowser.Model.SyncPlay; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.SyncPlay +{ + /// <summary> + /// Class Group. + /// </summary> + /// <remarks> + /// Class is not thread-safe, external locking is required when accessing methods. + /// </remarks> + public class Group : IGroupStateContext + { + /// <summary> + /// The logger. + /// </summary> + private readonly ILogger<Group> _logger; + + /// <summary> + /// The logger factory. + /// </summary> + private readonly ILoggerFactory _loggerFactory; + + /// <summary> + /// The user manager. + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// The session manager. + /// </summary> + private readonly ISessionManager _sessionManager; + + /// <summary> + /// The library manager. + /// </summary> + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// The participants, or members of the group. + /// </summary> + private readonly Dictionary<string, GroupMember> _participants = + new Dictionary<string, GroupMember>(StringComparer.OrdinalIgnoreCase); + + /// <summary> + /// The internal group state. + /// </summary> + private IGroupState _state; + + /// <summary> + /// Initializes a new instance of the <see cref="Group" /> class. + /// </summary> + /// <param name="loggerFactory">The logger factory.</param> + /// <param name="userManager">The user manager.</param> + /// <param name="sessionManager">The session manager.</param> + /// <param name="libraryManager">The library manager.</param> + public Group( + ILoggerFactory loggerFactory, + IUserManager userManager, + ISessionManager sessionManager, + ILibraryManager libraryManager) + { + _loggerFactory = loggerFactory; + _userManager = userManager; + _sessionManager = sessionManager; + _libraryManager = libraryManager; + _logger = loggerFactory.CreateLogger<Group>(); + + _state = new IdleGroupState(loggerFactory); + } + + /// <summary> + /// Gets the default ping value used for sessions. + /// </summary> + /// <value>The default ping.</value> + public long DefaultPing { get; } = 500; + + /// <summary> + /// Gets the maximum time offset error accepted for dates reported by clients, in milliseconds. + /// </summary> + /// <value>The maximum time offset error.</value> + public long TimeSyncOffset { get; } = 2000; + + /// <summary> + /// Gets the maximum offset error accepted for position reported by clients, in milliseconds. + /// </summary> + /// <value>The maximum offset error.</value> + public long MaxPlaybackOffset { get; } = 500; + + /// <summary> + /// Gets the group identifier. + /// </summary> + /// <value>The group identifier.</value> + public Guid GroupId { get; } = Guid.NewGuid(); + + /// <summary> + /// Gets the group name. + /// </summary> + /// <value>The group name.</value> + public string GroupName { get; private set; } + + /// <summary> + /// Gets the group identifier. + /// </summary> + /// <value>The group identifier.</value> + public PlayQueueManager PlayQueue { get; } = new PlayQueueManager(); + + /// <summary> + /// Gets the runtime ticks of current playing item. + /// </summary> + /// <value>The runtime ticks of current playing item.</value> + public long RunTimeTicks { get; private set; } + + /// <summary> + /// Gets or sets the position ticks. + /// </summary> + /// <value>The position ticks.</value> + public long PositionTicks { get; set; } + + /// <summary> + /// Gets or sets the last activity. + /// </summary> + /// <value>The last activity.</value> + public DateTime LastActivity { get; set; } + + /// <summary> + /// Adds the session to the group. + /// </summary> + /// <param name="session">The session.</param> + private void AddSession(SessionInfo session) + { + _participants.TryAdd( + session.Id, + new GroupMember(session) + { + Ping = DefaultPing, + IsBuffering = false + }); + } + + /// <summary> + /// Removes the session from the group. + /// </summary> + /// <param name="session">The session.</param> + private void RemoveSession(SessionInfo session) + { + _participants.Remove(session.Id); + } + + /// <summary> + /// Filters sessions of this group. + /// </summary> + /// <param name="fromId">The current session identifier.</param> + /// <param name="type">The filtering type.</param> + /// <returns>The list of sessions matching the filter.</returns> + private IEnumerable<string> FilterSessions(string fromId, SyncPlayBroadcastType type) + { + return type switch + { + SyncPlayBroadcastType.CurrentSession => new string[] { fromId }, + SyncPlayBroadcastType.AllGroup => _participants + .Values + .Select(member => member.SessionId), + SyncPlayBroadcastType.AllExceptCurrentSession => _participants + .Values + .Select(member => member.SessionId) + .Where(sessionId => !sessionId.Equals(fromId, StringComparison.OrdinalIgnoreCase)), + SyncPlayBroadcastType.AllReady => _participants + .Values + .Where(member => !member.IsBuffering) + .Select(member => member.SessionId), + _ => Enumerable.Empty<string>() + }; + } + + /// <summary> + /// Checks if a given user can access all items of a given queue, that is, + /// the user has the required minimum parental access and has access to all required folders. + /// </summary> + /// <param name="user">The user.</param> + /// <param name="queue">The queue.</param> + /// <returns><c>true</c> if the user can access all the items in the queue, <c>false</c> otherwise.</returns> + private bool HasAccessToQueue(User user, IReadOnlyList<Guid> queue) + { + // Check if queue is empty. + if (queue is null || queue.Count == 0) + { + return true; + } + + foreach (var itemId in queue) + { + var item = _libraryManager.GetItemById(itemId); + if (!item.IsVisibleStandalone(user)) + { + return false; + } + } + + return true; + } + + private bool AllUsersHaveAccessToQueue(IReadOnlyList<Guid> queue) + { + // Check if queue is empty. + if (queue is null || queue.Count == 0) + { + return true; + } + + // Get list of users. + var users = _participants + .Values + .Select(participant => _userManager.GetUserById(participant.UserId)); + + // Find problematic users. + var usersWithNoAccess = users.Where(user => !HasAccessToQueue(user, queue)); + + // All users must be able to access the queue. + return !usersWithNoAccess.Any(); + } + + /// <summary> + /// Checks if the group is empty. + /// </summary> + /// <returns><c>true</c> if the group is empty, <c>false</c> otherwise.</returns> + public bool IsGroupEmpty() => _participants.Count == 0; + + /// <summary> + /// Initializes the group with the session's info. + /// </summary> + /// <param name="session">The session.</param> + /// <param name="request">The request.</param> + /// <param name="cancellationToken">The cancellation token.</param> + public void CreateGroup(SessionInfo session, NewGroupRequest request, CancellationToken cancellationToken) + { + GroupName = request.GroupName; + AddSession(session); + + var sessionIsPlayingAnItem = session.FullNowPlayingItem is not null; + + RestartCurrentItem(); + + if (sessionIsPlayingAnItem) + { + var playlist = session.NowPlayingQueue.Select(item => item.Id).ToList(); + PlayQueue.Reset(); + PlayQueue.SetPlaylist(playlist); + PlayQueue.SetPlayingItemById(session.FullNowPlayingItem.Id); + RunTimeTicks = session.FullNowPlayingItem.RunTimeTicks ?? 0; + PositionTicks = session.PlayState.PositionTicks ?? 0; + + // Maintain playstate. + var waitingState = new WaitingGroupState(_loggerFactory) + { + ResumePlaying = !session.PlayState.IsPaused + }; + SetState(waitingState); + } + + var updateSession = new SyncPlayGroupJoinedUpdate(GroupId, GetInfo()); + SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken); + + _state.SessionJoined(this, _state.Type, session, cancellationToken); + + _logger.LogInformation("Session {SessionId} created group {GroupId}.", session.Id, GroupId.ToString()); + } + + /// <summary> + /// Adds the session to the group. + /// </summary> + /// <param name="session">The session.</param> + /// <param name="request">The request.</param> + /// <param name="cancellationToken">The cancellation token.</param> + public void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken) + { + AddSession(session); + + var updateSession = new SyncPlayGroupJoinedUpdate(GroupId, GetInfo()); + SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken); + + var updateOthers = new SyncPlayUserJoinedUpdate(GroupId, session.UserName); + SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); + + _state.SessionJoined(this, _state.Type, session, cancellationToken); + + _logger.LogInformation("Session {SessionId} joined group {GroupId}.", session.Id, GroupId.ToString()); + } + + /// <summary> + /// Removes the session from the group. + /// </summary> + /// <param name="session">The session.</param> + /// <param name="request">The request.</param> + /// <param name="cancellationToken">The cancellation token.</param> + public void SessionLeave(SessionInfo session, LeaveGroupRequest request, CancellationToken cancellationToken) + { + _state.SessionLeaving(this, _state.Type, session, cancellationToken); + + RemoveSession(session); + + var updateSession = new SyncPlayGroupLeftUpdate(GroupId, GroupId.ToString()); + SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken); + + var updateOthers = new SyncPlayUserLeftUpdate(GroupId, session.UserName); + SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); + + _logger.LogInformation("Session {SessionId} left group {GroupId}.", session.Id, GroupId.ToString()); + } + + /// <summary> + /// Handles the requested action by the session. + /// </summary> + /// <param name="session">The session.</param> + /// <param name="request">The requested action.</param> + /// <param name="cancellationToken">The cancellation token.</param> + public void HandleRequest(SessionInfo session, IGroupPlaybackRequest request, CancellationToken cancellationToken) + { + // The server's job is to maintain a consistent state for clients to reference + // and notify clients of state changes. The actual syncing of media playback + // happens client side. Clients are aware of the server's time and use it to sync. + _logger.LogInformation("Session {SessionId} requested {RequestType} in group {GroupId} that is {StateType}.", session.Id, request.Action, GroupId.ToString(), _state.Type); + + // Apply requested changes to this group given its current state. + // Every request has a slightly different outcome depending on the group's state. + // There are currently four different group states that accomplish different goals: + // - Idle: in this state no media is playing and clients should be idle (playback is stopped). + // - Waiting: in this state the group is waiting for all the clients to be ready to start the playback, + // that is, they've either finished loading the media for the first time or they've finished buffering. + // Once all clients report to be ready the group's state can change to Playing or Paused. + // - Playing: clients have some media loaded and playback is unpaused. + // - Paused: clients have some media loaded but playback is currently paused. + request.Apply(this, _state, session, cancellationToken); + } + + /// <summary> + /// Gets the info about the group for the clients. + /// </summary> + /// <returns>The group info for the clients.</returns> + public GroupInfoDto GetInfo() + { + var participants = _participants.Values.Select(session => session.UserName).Distinct().ToList(); + return new GroupInfoDto(GroupId, GroupName, _state.Type, participants, DateTime.UtcNow); + } + + /// <summary> + /// Checks if a user has access to all content in the play queue. + /// </summary> + /// <param name="user">The user.</param> + /// <returns><c>true</c> if the user can access the play queue; <c>false</c> otherwise.</returns> + public bool HasAccessToPlayQueue(User user) + { + var items = PlayQueue.GetPlaylist().Select(item => item.ItemId).ToList(); + return HasAccessToQueue(user, items); + } + + /// <inheritdoc /> + public void SetIgnoreGroupWait(SessionInfo session, bool ignoreGroupWait) + { + if (_participants.TryGetValue(session.Id, out GroupMember value)) + { + value.IgnoreGroupWait = ignoreGroupWait; + } + } + + /// <inheritdoc /> + public void SetState(IGroupState state) + { + _logger.LogInformation("Group {GroupId} switching from {FromStateType} to {ToStateType}.", GroupId.ToString(), _state.Type, state.Type); + this._state = state; + } + + /// <inheritdoc /> + public Task SendGroupUpdate<T>(SessionInfo from, SyncPlayBroadcastType type, GroupUpdate<T> message, CancellationToken cancellationToken) + { + IEnumerable<Task> GetTasks() + { + foreach (var sessionId in FilterSessions(from.Id, type)) + { + yield return _sessionManager.SendSyncPlayGroupUpdate(sessionId, message, cancellationToken); + } + } + + return Task.WhenAll(GetTasks()); + } + + /// <inheritdoc /> + public Task SendCommand(SessionInfo from, SyncPlayBroadcastType type, SendCommand message, CancellationToken cancellationToken) + { + IEnumerable<Task> GetTasks() + { + foreach (var sessionId in FilterSessions(from.Id, type)) + { + yield return _sessionManager.SendSyncPlayCommand(sessionId, message, cancellationToken); + } + } + + return Task.WhenAll(GetTasks()); + } + + /// <inheritdoc /> + public SendCommand NewSyncPlayCommand(SendCommandType type) + { + return new SendCommand( + GroupId, + PlayQueue.GetPlayingItemPlaylistId(), + LastActivity, + type, + PositionTicks, + DateTime.UtcNow); + } + + /// <inheritdoc /> + public long SanitizePositionTicks(long? positionTicks) + { + var ticks = positionTicks ?? 0; + return Math.Clamp(ticks, 0, RunTimeTicks); + } + + /// <inheritdoc /> + public void UpdatePing(SessionInfo session, long ping) + { + if (_participants.TryGetValue(session.Id, out GroupMember value)) + { + value.Ping = ping; + } + } + + /// <inheritdoc /> + public long GetHighestPing() + { + long max = long.MinValue; + foreach (var session in _participants.Values) + { + max = Math.Max(max, session.Ping); + } + + return max; + } + + /// <inheritdoc /> + public void SetBuffering(SessionInfo session, bool isBuffering) + { + if (_participants.TryGetValue(session.Id, out GroupMember value)) + { + value.IsBuffering = isBuffering; + } + } + + /// <inheritdoc /> + public void SetAllBuffering(bool isBuffering) + { + foreach (var session in _participants.Values) + { + session.IsBuffering = isBuffering; + } + } + + /// <inheritdoc /> + public bool IsBuffering() + { + foreach (var session in _participants.Values) + { + if (session.IsBuffering && !session.IgnoreGroupWait) + { + return true; + } + } + + return false; + } + + /// <inheritdoc /> + public bool SetPlayQueue(IReadOnlyList<Guid> playQueue, int playingItemPosition, long startPositionTicks) + { + // Ignore on empty queue or invalid item position. + if (playQueue.Count == 0 || playingItemPosition >= playQueue.Count || playingItemPosition < 0) + { + return false; + } + + // Check if participants can access the new playing queue. + if (!AllUsersHaveAccessToQueue(playQueue)) + { + return false; + } + + PlayQueue.Reset(); + PlayQueue.SetPlaylist(playQueue); + PlayQueue.SetPlayingItemByIndex(playingItemPosition); + var item = _libraryManager.GetItemById(PlayQueue.GetPlayingItemId()); + RunTimeTicks = item.RunTimeTicks ?? 0; + PositionTicks = startPositionTicks; + LastActivity = DateTime.UtcNow; + + return true; + } + + /// <inheritdoc /> + public bool SetPlayingItem(Guid playlistItemId) + { + var itemFound = PlayQueue.SetPlayingItemByPlaylistId(playlistItemId); + + if (itemFound) + { + var item = _libraryManager.GetItemById(PlayQueue.GetPlayingItemId()); + RunTimeTicks = item.RunTimeTicks ?? 0; + } + else + { + RunTimeTicks = 0; + } + + RestartCurrentItem(); + + return itemFound; + } + + /// <inheritdoc /> + public void ClearPlayQueue(bool clearPlayingItem) + { + PlayQueue.ClearPlaylist(clearPlayingItem); + if (clearPlayingItem) + { + RestartCurrentItem(); + } + } + + /// <inheritdoc /> + public bool RemoveFromPlayQueue(IReadOnlyList<Guid> playlistItemIds) + { + var playingItemRemoved = PlayQueue.RemoveFromPlaylist(playlistItemIds); + if (playingItemRemoved) + { + var itemId = PlayQueue.GetPlayingItemId(); + if (!itemId.IsEmpty()) + { + var item = _libraryManager.GetItemById(itemId); + RunTimeTicks = item.RunTimeTicks ?? 0; + } + else + { + RunTimeTicks = 0; + } + + RestartCurrentItem(); + } + + return playingItemRemoved; + } + + /// <inheritdoc /> + public bool MoveItemInPlayQueue(Guid playlistItemId, int newIndex) + { + return PlayQueue.MovePlaylistItem(playlistItemId, newIndex); + } + + /// <inheritdoc /> + public bool AddToPlayQueue(IReadOnlyList<Guid> newItems, GroupQueueMode mode) + { + // Ignore on empty list. + if (newItems.Count == 0) + { + return false; + } + + // Check if participants can access the new playing queue. + if (!AllUsersHaveAccessToQueue(newItems)) + { + return false; + } + + if (mode.Equals(GroupQueueMode.QueueNext)) + { + PlayQueue.QueueNext(newItems); + } + else + { + PlayQueue.Queue(newItems); + } + + return true; + } + + /// <inheritdoc /> + public void RestartCurrentItem() + { + PositionTicks = 0; + LastActivity = DateTime.UtcNow; + } + + /// <inheritdoc /> + public bool NextItemInQueue() + { + var update = PlayQueue.Next(); + if (update) + { + var item = _libraryManager.GetItemById(PlayQueue.GetPlayingItemId()); + RunTimeTicks = item.RunTimeTicks ?? 0; + RestartCurrentItem(); + return true; + } + + return false; + } + + /// <inheritdoc /> + public bool PreviousItemInQueue() + { + var update = PlayQueue.Previous(); + if (update) + { + var item = _libraryManager.GetItemById(PlayQueue.GetPlayingItemId()); + RunTimeTicks = item.RunTimeTicks ?? 0; + RestartCurrentItem(); + return true; + } + + return false; + } + + /// <inheritdoc /> + public void SetRepeatMode(GroupRepeatMode mode) + { + PlayQueue.SetRepeatMode(mode); + } + + /// <inheritdoc /> + public void SetShuffleMode(GroupShuffleMode mode) + { + PlayQueue.SetShuffleMode(mode); + } + + /// <inheritdoc /> + public PlayQueueUpdate GetPlayQueueUpdate(PlayQueueUpdateReason reason) + { + var startPositionTicks = PositionTicks; + var isPlaying = _state.Type.Equals(GroupStateType.Playing); + + if (isPlaying) + { + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - LastActivity; + // Elapsed time is negative if event happens + // during the delay added to account for latency. + // In this phase clients haven't started the playback yet. + // In other words, LastActivity is in the future, + // when playback unpause is supposed to happen. + // Adjust ticks only if playback actually started. + startPositionTicks += Math.Max(elapsedTime.Ticks, 0); + } + + return new PlayQueueUpdate( + reason, + PlayQueue.LastChange, + PlayQueue.GetPlaylist(), + PlayQueue.PlayingItemIndex, + startPositionTicks, + isPlaying, + PlayQueue.ShuffleMode, + PlayQueue.RepeatMode); + } + } +} diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs new file mode 100644 index 00000000..b45d7545 --- /dev/null +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -0,0 +1,419 @@ +#nullable disable + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.SyncPlay; +using MediaBrowser.Controller.SyncPlay.Requests; +using MediaBrowser.Model.SyncPlay; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.SyncPlay +{ + /// <summary> + /// Class SyncPlayManager. + /// </summary> + public class SyncPlayManager : ISyncPlayManager, IDisposable + { + /// <summary> + /// The logger. + /// </summary> + private readonly ILogger<SyncPlayManager> _logger; + + /// <summary> + /// The logger factory. + /// </summary> + private readonly ILoggerFactory _loggerFactory; + + /// <summary> + /// The user manager. + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// The session manager. + /// </summary> + private readonly ISessionManager _sessionManager; + + /// <summary> + /// The library manager. + /// </summary> + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// The map between users and counter of active sessions. + /// </summary> + private readonly ConcurrentDictionary<Guid, int> _activeUsers = + new ConcurrentDictionary<Guid, int>(); + + /// <summary> + /// The map between sessions and groups. + /// </summary> + private readonly ConcurrentDictionary<string, Group> _sessionToGroupMap = + new ConcurrentDictionary<string, Group>(StringComparer.OrdinalIgnoreCase); + + /// <summary> + /// The groups. + /// </summary> + private readonly ConcurrentDictionary<Guid, Group> _groups = + new ConcurrentDictionary<Guid, Group>(); + + /// <summary> + /// Lock used for accessing multiple groups at once. + /// </summary> + /// <remarks> + /// This lock has priority on locks made on <see cref="Group"/>. + /// </remarks> + private readonly Lock _groupsLock = new(); + + private bool _disposed = false; + + /// <summary> + /// Initializes a new instance of the <see cref="SyncPlayManager" /> class. + /// </summary> + /// <param name="loggerFactory">The logger factory.</param> + /// <param name="userManager">The user manager.</param> + /// <param name="sessionManager">The session manager.</param> + /// <param name="libraryManager">The library manager.</param> + public SyncPlayManager( + ILoggerFactory loggerFactory, + IUserManager userManager, + ISessionManager sessionManager, + ILibraryManager libraryManager) + { + _loggerFactory = loggerFactory; + _userManager = userManager; + _sessionManager = sessionManager; + _libraryManager = libraryManager; + _logger = loggerFactory.CreateLogger<SyncPlayManager>(); + _sessionManager.SessionEnded += OnSessionEnded; + } + + /// <inheritdoc /> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <inheritdoc /> + public GroupInfoDto NewGroup(SessionInfo session, NewGroupRequest request, CancellationToken cancellationToken) + { + if (session is null) + { + throw new InvalidOperationException("Session is null!"); + } + + if (request is null) + { + throw new InvalidOperationException("Request is null!"); + } + + // Locking required to access list of groups. + lock (_groupsLock) + { + // Make sure that session has not joined another group. + if (_sessionToGroupMap.ContainsKey(session.Id)) + { + var leaveGroupRequest = new LeaveGroupRequest(); + LeaveGroup(session, leaveGroupRequest, cancellationToken); + } + + var group = new Group(_loggerFactory, _userManager, _sessionManager, _libraryManager); + _groups[group.GroupId] = group; + + if (!_sessionToGroupMap.TryAdd(session.Id, group)) + { + throw new InvalidOperationException("Could not add session to group!"); + } + + UpdateSessionsCounter(session.UserId, 1); + group.CreateGroup(session, request, cancellationToken); + return group.GetInfo(); + } + } + + /// <inheritdoc /> + public void JoinGroup(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken) + { + if (session is null) + { + throw new InvalidOperationException("Session is null!"); + } + + if (request is null) + { + throw new InvalidOperationException("Request is null!"); + } + + var user = _userManager.GetUserById(session.UserId); + + // Locking required to access list of groups. + lock (_groupsLock) + { + _groups.TryGetValue(request.GroupId, out Group group); + + if (group is null) + { + _logger.LogWarning("Session {SessionId} tried to join group {GroupId} that does not exist.", session.Id, request.GroupId); + + var error = new SyncPlayGroupDoesNotExistUpdate(Guid.Empty, string.Empty); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); + return; + } + + // Group lock required to let other requests end first. + lock (group) + { + if (!group.HasAccessToPlayQueue(user)) + { + _logger.LogWarning("Session {SessionId} tried to join group {GroupId} but does not have access to some content of the playing queue.", session.Id, group.GroupId.ToString()); + + var error = new SyncPlayLibraryAccessDeniedUpdate(group.GroupId, string.Empty); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); + return; + } + + if (_sessionToGroupMap.TryGetValue(session.Id, out var existingGroup)) + { + if (existingGroup.GroupId.Equals(request.GroupId)) + { + // Restore session. + UpdateSessionsCounter(session.UserId, 1); + group.SessionJoin(session, request, cancellationToken); + return; + } + + var leaveGroupRequest = new LeaveGroupRequest(); + LeaveGroup(session, leaveGroupRequest, cancellationToken); + } + + if (!_sessionToGroupMap.TryAdd(session.Id, group)) + { + throw new InvalidOperationException("Could not add session to group!"); + } + + UpdateSessionsCounter(session.UserId, 1); + group.SessionJoin(session, request, cancellationToken); + } + } + } + + /// <inheritdoc /> + public void LeaveGroup(SessionInfo session, LeaveGroupRequest request, CancellationToken cancellationToken) + { + if (session is null) + { + throw new InvalidOperationException("Session is null!"); + } + + if (request is null) + { + throw new InvalidOperationException("Request is null!"); + } + + // Locking required to access list of groups. + lock (_groupsLock) + { + if (_sessionToGroupMap.TryGetValue(session.Id, out var group)) + { + // Group lock required to let other requests end first. + lock (group) + { + if (_sessionToGroupMap.TryRemove(session.Id, out var tempGroup)) + { + if (!tempGroup.GroupId.Equals(group.GroupId)) + { + throw new InvalidOperationException("Session was in wrong group!"); + } + } + else + { + throw new InvalidOperationException("Could not remove session from group!"); + } + + UpdateSessionsCounter(session.UserId, -1); + group.SessionLeave(session, request, cancellationToken); + + if (group.IsGroupEmpty()) + { + _logger.LogInformation("Group {GroupId} is empty, removing it.", group.GroupId); + _groups.Remove(group.GroupId, out _); + } + } + } + else + { + _logger.LogWarning("Session {SessionId} does not belong to any group.", session.Id); + + var error = new SyncPlayNotInGroupUpdate(Guid.Empty, string.Empty); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); + } + } + } + + /// <inheritdoc /> + public List<GroupInfoDto> ListGroups(SessionInfo session, ListGroupsRequest request) + { + if (session is null) + { + throw new InvalidOperationException("Session is null!"); + } + + if (request is null) + { + throw new InvalidOperationException("Request is null!"); + } + + var user = _userManager.GetUserById(session.UserId); + List<GroupInfoDto> list = new List<GroupInfoDto>(); + + lock (_groupsLock) + { + foreach (var (_, group) in _groups) + { + // Locking required as group is not thread-safe. + lock (group) + { + if (group.HasAccessToPlayQueue(user)) + { + list.Add(group.GetInfo()); + } + } + } + } + + return list; + } + + /// <inheritdoc /> + public GroupInfoDto GetGroup(SessionInfo session, Guid groupId) + { + ArgumentNullException.ThrowIfNull(session); + + var user = _userManager.GetUserById(session.UserId); + + lock (_groupsLock) + { + foreach (var (_, group) in _groups) + { + // Locking required as group is not thread-safe. + lock (group) + { + if (group.GroupId.Equals(groupId) && group.HasAccessToPlayQueue(user)) + { + return group.GetInfo(); + } + } + } + } + + return null; + } + + /// <inheritdoc /> + public void HandleRequest(SessionInfo session, IGroupPlaybackRequest request, CancellationToken cancellationToken) + { + if (session is null) + { + throw new InvalidOperationException("Session is null!"); + } + + if (request is null) + { + throw new InvalidOperationException("Request is null!"); + } + + if (_sessionToGroupMap.TryGetValue(session.Id, out var group)) + { + // Group lock required as Group is not thread-safe. + lock (group) + { + // Make sure that session still belongs to this group. + if (_sessionToGroupMap.TryGetValue(session.Id, out var checkGroup) && !checkGroup.GroupId.Equals(group.GroupId)) + { + // Drop request. + return; + } + + // Drop request if group is empty. + if (group.IsGroupEmpty()) + { + return; + } + + // Apply requested changes to group. + group.HandleRequest(session, request, cancellationToken); + } + } + else + { + _logger.LogWarning("Session {SessionId} does not belong to any group.", session.Id); + + var error = new SyncPlayNotInGroupUpdate(Guid.Empty, string.Empty); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); + } + } + + /// <inheritdoc /> + public bool IsUserActive(Guid userId) + { + if (_activeUsers.TryGetValue(userId, out var sessionsCounter)) + { + return sessionsCounter > 0; + } + + return false; + } + + /// <summary> + /// Releases unmanaged and optionally managed resources. + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _sessionManager.SessionEnded -= OnSessionEnded; + _disposed = true; + } + + private void OnSessionEnded(object sender, SessionEventArgs e) + { + var session = e.SessionInfo; + + if (_sessionToGroupMap.TryGetValue(session.Id, out _)) + { + var leaveGroupRequest = new LeaveGroupRequest(); + LeaveGroup(session, leaveGroupRequest, CancellationToken.None); + } + } + + private void UpdateSessionsCounter(Guid userId, int toAdd) + { + // Update sessions counter. + var newSessionsCounter = _activeUsers.AddOrUpdate( + userId, + 1, + (_, sessionsCounter) => sessionsCounter + toAdd); + + // Should never happen. + if (newSessionsCounter < 0) + { + throw new InvalidOperationException("Sessions counter is negative!"); + } + + // Clean record if user has no more active sessions. + if (newSessionsCounter == 0) + { + _activeUsers.TryRemove(new KeyValuePair<Guid, int>(userId, newSessionsCounter)); + } + } + } +} diff --git a/Emby.Server.Implementations/SystemManager.cs b/Emby.Server.Implementations/SystemManager.cs new file mode 100644 index 00000000..d140426d --- /dev/null +++ b/Emby.Server.Implementations/SystemManager.cs @@ -0,0 +1,140 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Server.Implementations.StorageHelpers; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.System; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Hosting; + +namespace Emby.Server.Implementations; + +/// <inheritdoc /> +public class SystemManager : ISystemManager +{ + private readonly IHostApplicationLifetime _applicationLifetime; + private readonly IServerApplicationHost _applicationHost; + private readonly IServerApplicationPaths _applicationPaths; + private readonly IServerConfigurationManager _configurationManager; + private readonly IStartupOptions _startupOptions; + private readonly IInstallationManager _installationManager; + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// Initializes a new instance of the <see cref="SystemManager"/> class. + /// </summary> + /// <param name="applicationLifetime">Instance of <see cref="IHostApplicationLifetime"/>.</param> + /// <param name="applicationHost">Instance of <see cref="IServerApplicationHost"/>.</param> + /// <param name="applicationPaths">Instance of <see cref="IServerApplicationPaths"/>.</param> + /// <param name="configurationManager">Instance of <see cref="IServerConfigurationManager"/>.</param> + /// <param name="startupOptions">Instance of <see cref="IStartupOptions"/>.</param> + /// <param name="installationManager">Instance of <see cref="IInstallationManager"/>.</param> + /// <param name="libraryManager">Instance of <see cref="ILibraryManager"/>.</param> + public SystemManager( + IHostApplicationLifetime applicationLifetime, + IServerApplicationHost applicationHost, + IServerApplicationPaths applicationPaths, + IServerConfigurationManager configurationManager, + IStartupOptions startupOptions, + IInstallationManager installationManager, + ILibraryManager libraryManager) + { + _applicationLifetime = applicationLifetime; + _applicationHost = applicationHost; + _applicationPaths = applicationPaths; + _configurationManager = configurationManager; + _startupOptions = startupOptions; + _installationManager = installationManager; + _libraryManager = libraryManager; + } + + /// <inheritdoc /> + public SystemInfo GetSystemInfo(HttpRequest request) + { + return new SystemInfo + { + HasPendingRestart = _applicationHost.HasPendingRestart, + IsShuttingDown = _applicationLifetime.ApplicationStopping.IsCancellationRequested, + Version = _applicationHost.ApplicationVersionString, + ProductName = _applicationHost.Name, + WebSocketPortNumber = _applicationHost.HttpPort, + CompletedInstallations = _installationManager.CompletedInstallations.ToArray(), + Id = _applicationHost.SystemId, +#pragma warning disable CS0618 // Type or member is obsolete + ProgramDataPath = _applicationPaths.ProgramDataPath, + WebPath = _applicationPaths.WebPath, + LogPath = _applicationPaths.LogDirectoryPath, + ItemsByNamePath = _applicationPaths.InternalMetadataPath, + InternalMetadataPath = _applicationPaths.InternalMetadataPath, + CachePath = _applicationPaths.CachePath, + TranscodingTempPath = _configurationManager.GetTranscodePath(), +#pragma warning restore CS0618 // Type or member is obsolete + ServerName = _applicationHost.FriendlyName, + LocalAddress = _applicationHost.GetSmartApiUrl(request), + StartupWizardCompleted = _configurationManager.CommonConfiguration.IsStartupWizardCompleted, + SupportsLibraryMonitor = true, + PackageName = _startupOptions.PackageName, + CastReceiverApplications = _configurationManager.Configuration.CastReceiverApplications + }; + } + + /// <inheritdoc/> + public SystemStorageInfo GetSystemStorageInfo() + { + var virtualFolderInfos = _libraryManager + .GetVirtualFolders() + .Where(e => !string.IsNullOrWhiteSpace(e.ItemId)) // this should not be null but for some users it is. + .Select(e => new LibraryStorageInfo() + { + Id = Guid.Parse(e.ItemId), + Name = e.Name, + Folders = e.Locations.Select(f => StorageHelper.GetFreeSpaceOf(f)).ToArray() + }); + + return new SystemStorageInfo() + { + ProgramDataFolder = StorageHelper.GetFreeSpaceOf(_applicationPaths.ProgramDataPath), + WebFolder = StorageHelper.GetFreeSpaceOf(_applicationPaths.WebPath), + LogFolder = StorageHelper.GetFreeSpaceOf(_applicationPaths.LogDirectoryPath), + ImageCacheFolder = StorageHelper.GetFreeSpaceOf(_applicationPaths.ImageCachePath), + InternalMetadataFolder = StorageHelper.GetFreeSpaceOf(_applicationPaths.InternalMetadataPath), + CacheFolder = StorageHelper.GetFreeSpaceOf(_applicationPaths.CachePath), + TranscodingTempFolder = StorageHelper.GetFreeSpaceOf(_configurationManager.GetTranscodePath()), + Libraries = virtualFolderInfos.ToArray() + }; + } + + /// <inheritdoc /> + public PublicSystemInfo GetPublicSystemInfo(HttpRequest request) + { + return new PublicSystemInfo + { + Version = _applicationHost.ApplicationVersionString, + ProductName = _applicationHost.Name, + Id = _applicationHost.SystemId, + ServerName = _applicationHost.FriendlyName, + LocalAddress = _applicationHost.GetSmartApiUrl(request), + StartupWizardCompleted = _configurationManager.CommonConfiguration.IsStartupWizardCompleted + }; + } + + /// <inheritdoc /> + public void Restart() => ShutdownInternal(true); + + /// <inheritdoc /> + public void Shutdown() => ShutdownInternal(false); + + private void ShutdownInternal(bool restart) + { + Task.Run(async () => + { + await Task.Delay(100).ConfigureAwait(false); + _applicationHost.ShouldRestart = restart; + _applicationLifetime.StopApplication(); + }); + } +} diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs new file mode 100644 index 00000000..cd98dbe8 --- /dev/null +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -0,0 +1,280 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Linq; +using Jellyfin.Data; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.TV; +using MediaBrowser.Model.Querying; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Series = MediaBrowser.Controller.Entities.TV.Series; + +namespace Emby.Server.Implementations.TV +{ + public class TVSeriesManager : ITVSeriesManager + { + private readonly IUserDataManager _userDataManager; + private readonly ILibraryManager _libraryManager; + private readonly IServerConfigurationManager _configurationManager; + + public TVSeriesManager(IUserDataManager userDataManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager) + { + _userDataManager = userDataManager; + _libraryManager = libraryManager; + _configurationManager = configurationManager; + } + + public QueryResult<BaseItem> GetNextUp(NextUpQuery query, DtoOptions options) + { + var user = query.User; + + string? presentationUniqueKey = null; + if (!query.SeriesId.IsNullOrEmpty()) + { + if (_libraryManager.GetItemById(query.SeriesId.Value) is Series series) + { + presentationUniqueKey = GetUniqueSeriesKey(series); + } + } + + if (!string.IsNullOrEmpty(presentationUniqueKey)) + { + return GetResult(GetNextUpEpisodes(query, user, new[] { presentationUniqueKey }, options), query); + } + + BaseItem[] parents; + + if (query.ParentId.HasValue) + { + var parent = _libraryManager.GetItemById(query.ParentId.Value); + + if (parent is not null) + { + parents = new[] { parent }; + } + else + { + parents = Array.Empty<BaseItem>(); + } + } + else + { + parents = _libraryManager.GetUserRootFolder().GetChildren(user, true) + .Where(i => i is Folder) + .Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes).Contains(i.Id)) + .ToArray(); + } + + return GetNextUp(query, parents, options); + } + + public QueryResult<BaseItem> GetNextUp(NextUpQuery request, BaseItem[] parentsFolders, DtoOptions options) + { + var user = request.User; + + string? presentationUniqueKey = null; + int? limit = null; + if (!request.SeriesId.IsNullOrEmpty()) + { + if (_libraryManager.GetItemById(request.SeriesId.Value) is Series series) + { + presentationUniqueKey = GetUniqueSeriesKey(series); + limit = 1; + } + } + + if (!string.IsNullOrEmpty(presentationUniqueKey)) + { + return GetResult(GetNextUpEpisodes(request, user, [presentationUniqueKey], options), request); + } + + if (limit.HasValue) + { + limit = limit.Value + 10; + } + + var nextUpSeriesKeys = _libraryManager.GetNextUpSeriesKeys(new InternalItemsQuery(user) { Limit = limit }, parentsFolders, request.NextUpDateCutoff); + + var episodes = GetNextUpEpisodes(request, user, nextUpSeriesKeys, options); + + return GetResult(episodes, request); + } + + private IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IReadOnlyList<string> seriesKeys, DtoOptions dtoOptions) + { + var allNextUp = seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, request.EnableResumable, false)); + + if (request.EnableRewatching) + { + allNextUp = allNextUp + .Concat(seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, false, true))) + .OrderByDescending(i => i.LastWatchedDate); + } + + return allNextUp + .Select(i => i.GetEpisodeFunction()) + .Where(i => i is not null)!; + } + + private static string GetUniqueSeriesKey(Series series) + { + return series.GetPresentationUniqueKey(); + } + + /// <summary> + /// Gets the next up. + /// </summary> + /// <returns>Task{Episode}.</returns> + private (DateTime LastWatchedDate, Func<Episode?> GetEpisodeFunction) GetNextUp(string seriesKey, User user, DtoOptions dtoOptions, bool includeResumable, bool includePlayed) + { + var lastQuery = new InternalItemsQuery(user) + { + AncestorWithPresentationUniqueKey = null, + SeriesPresentationUniqueKey = seriesKey, + IncludeItemTypes = [BaseItemKind.Episode], + IsPlayed = true, + Limit = 1, + ParentIndexNumberNotEquals = 0, + DtoOptions = new DtoOptions + { + Fields = [ItemFields.SortName], + EnableImages = false + } + }; + + // If including played results, sort first by date played and then by season and episode numbers + lastQuery.OrderBy = includePlayed + ? new[] { (ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.ParentIndexNumber, SortOrder.Descending), (ItemSortBy.IndexNumber, SortOrder.Descending) } + : new[] { (ItemSortBy.ParentIndexNumber, SortOrder.Descending), (ItemSortBy.IndexNumber, SortOrder.Descending) }; + + var lastWatchedEpisode = _libraryManager.GetItemList(lastQuery).Cast<Episode>().FirstOrDefault(); + + Episode? GetEpisode() + { + var nextQuery = new InternalItemsQuery(user) + { + AncestorWithPresentationUniqueKey = null, + SeriesPresentationUniqueKey = seriesKey, + IncludeItemTypes = [BaseItemKind.Episode], + OrderBy = [(ItemSortBy.ParentIndexNumber, SortOrder.Ascending), (ItemSortBy.IndexNumber, SortOrder.Ascending)], + Limit = 1, + IsPlayed = includePlayed, + IsVirtualItem = false, + ParentIndexNumberNotEquals = 0, + DtoOptions = dtoOptions + }; + + // Locate the next up episode based on the last watched episode's season and episode number + var lastWatchedParentIndexNumber = lastWatchedEpisode?.ParentIndexNumber; + var lastWatchedIndexNumber = lastWatchedEpisode?.IndexNumberEnd ?? lastWatchedEpisode?.IndexNumber; + if (lastWatchedParentIndexNumber.HasValue && lastWatchedIndexNumber.HasValue) + { + nextQuery.MinParentAndIndexNumber = (lastWatchedParentIndexNumber.Value, lastWatchedIndexNumber.Value + 1); + } + + var nextEpisode = _libraryManager.GetItemList(nextQuery).Cast<Episode>().FirstOrDefault(); + + if (_configurationManager.Configuration.DisplaySpecialsWithinSeasons) + { + var consideredEpisodes = _libraryManager.GetItemList(new InternalItemsQuery(user) + { + AncestorWithPresentationUniqueKey = null, + SeriesPresentationUniqueKey = seriesKey, + ParentIndexNumber = 0, + IncludeItemTypes = [BaseItemKind.Episode], + IsPlayed = includePlayed, + IsVirtualItem = false, + DtoOptions = dtoOptions + }) + .Cast<Episode>() + .Where(episode => episode.AirsBeforeSeasonNumber is not null || episode.AirsAfterSeasonNumber is not null) + .ToList(); + + if (lastWatchedEpisode is not null) + { + // Last watched episode is added, because there could be specials that aired before the last watched episode + consideredEpisodes.Add(lastWatchedEpisode); + } + + if (nextEpisode is not null) + { + consideredEpisodes.Add(nextEpisode); + } + + var sortedConsideredEpisodes = _libraryManager.Sort(consideredEpisodes, user, [(ItemSortBy.AiredEpisodeOrder, SortOrder.Ascending)]) + .Cast<Episode>(); + if (lastWatchedEpisode is not null) + { + sortedConsideredEpisodes = sortedConsideredEpisodes.SkipWhile(episode => !episode.Id.Equals(lastWatchedEpisode.Id)).Skip(1); + } + + nextEpisode = sortedConsideredEpisodes.FirstOrDefault(); + } + + if (nextEpisode is not null && !includeResumable) + { + var userData = _userDataManager.GetUserData(user, nextEpisode); + + if (userData?.PlaybackPositionTicks > 0) + { + return null; + } + } + + return nextEpisode; + } + + if (lastWatchedEpisode is not null) + { + var userData = _userDataManager.GetUserData(user, lastWatchedEpisode); + + if (userData is null) + { + return (DateTime.MinValue, GetEpisode); + } + + var lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue.AddDays(1); + + return (lastWatchedDate, GetEpisode); + } + + // Return the first episode + return (DateTime.MinValue, GetEpisode); + } + + private static QueryResult<BaseItem> GetResult(IEnumerable<BaseItem> items, NextUpQuery query) + { + int totalCount = 0; + + if (query.EnableTotalRecordCount) + { + var list = items.ToList(); + totalCount = list.Count; + items = list; + } + + if (query.StartIndex.HasValue) + { + items = items.Skip(query.StartIndex.Value); + } + + if (query.Limit.HasValue && query.Limit.Value > 0) + { + items = items.Take(query.Limit.Value); + } + + return new QueryResult<BaseItem>( + query.StartIndex, + totalCount, + items.ToArray()); + } + } +} diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs new file mode 100644 index 00000000..67b77a11 --- /dev/null +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -0,0 +1,584 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Json; +using System.Security.Cryptography; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Events; +using Jellyfin.Extensions; +using Jellyfin.Extensions.Json; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Updates; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Updates; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Updates +{ + /// <summary> + /// Manages all install, uninstall, and update operations for the system and individual plugins. + /// </summary> + public class InstallationManager : IInstallationManager + { + /// <summary> + /// The logger. + /// </summary> + private readonly ILogger<InstallationManager> _logger; + private readonly IApplicationPaths _appPaths; + private readonly IEventManager _eventManager; + private readonly IHttpClientFactory _httpClientFactory; + private readonly IServerConfigurationManager _config; + private readonly JsonSerializerOptions _jsonSerializerOptions; + private readonly IPluginManager _pluginManager; + + /// <summary> + /// Gets the application host. + /// </summary> + /// <value>The application host.</value> + private readonly IServerApplicationHost _applicationHost; + private readonly Lock _currentInstallationsLock = new(); + + /// <summary> + /// The current installations. + /// </summary> + private readonly List<(InstallationInfo Info, CancellationTokenSource Token)> _currentInstallations; + + /// <summary> + /// The completed installations. + /// </summary> + private readonly ConcurrentBag<InstallationInfo> _completedInstallationsInternal; + + /// <summary> + /// Initializes a new instance of the <see cref="InstallationManager"/> class. + /// </summary> + /// <param name="logger">The <see cref="ILogger{InstallationManager}"/>.</param> + /// <param name="appHost">The <see cref="IServerApplicationHost"/>.</param> + /// <param name="appPaths">The <see cref="IApplicationPaths"/>.</param> + /// <param name="eventManager">The <see cref="IEventManager"/>.</param> + /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param> + /// <param name="config">The <see cref="IServerConfigurationManager"/>.</param> + /// <param name="pluginManager">The <see cref="IPluginManager"/>.</param> + public InstallationManager( + ILogger<InstallationManager> logger, + IServerApplicationHost appHost, + IApplicationPaths appPaths, + IEventManager eventManager, + IHttpClientFactory httpClientFactory, + IServerConfigurationManager config, + IPluginManager pluginManager) + { + _currentInstallations = new List<(InstallationInfo, CancellationTokenSource)>(); + _completedInstallationsInternal = new ConcurrentBag<InstallationInfo>(); + + _logger = logger; + _applicationHost = appHost; + _appPaths = appPaths; + _eventManager = eventManager; + _httpClientFactory = httpClientFactory; + _config = config; + _jsonSerializerOptions = JsonDefaults.Options; + _pluginManager = pluginManager; + } + + /// <inheritdoc /> + public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal; + + /// <inheritdoc /> + public async Task<PackageInfo[]> GetPackages(string manifestName, string manifest, bool filterIncompatible, CancellationToken cancellationToken = default) + { + try + { + PackageInfo[]? packages = await _httpClientFactory.CreateClient(NamedClient.Default) + .GetFromJsonAsync<PackageInfo[]>(new Uri(manifest), _jsonSerializerOptions, cancellationToken).ConfigureAwait(false); + + if (packages is null) + { + return Array.Empty<PackageInfo>(); + } + + var minimumVersion = new Version(0, 0, 0, 1); + // Store the repository and repository url with each version, as they may be spread apart. + foreach (var entry in packages) + { + for (int a = entry.Versions.Count - 1; a >= 0; a--) + { + var ver = entry.Versions[a]; + ver.RepositoryName = manifestName; + ver.RepositoryUrl = manifest; + + if (!filterIncompatible) + { + continue; + } + + if (!Version.TryParse(ver.TargetAbi, out var targetAbi)) + { + targetAbi = minimumVersion; + } + + // Only show plugins that are greater than or equal to targetAbi. + if (_applicationHost.ApplicationVersion >= targetAbi) + { + continue; + } + + // Not compatible with this version so remove it. + entry.Versions.Remove(ver); + } + } + + return packages; + } + catch (IOException ex) + { + _logger.LogError(ex, "Cannot locate the plugin manifest {Manifest}", manifest); + return Array.Empty<PackageInfo>(); + } + catch (JsonException ex) + { + _logger.LogError(ex, "Failed to deserialize the plugin manifest retrieved from {Manifest}", manifest); + return Array.Empty<PackageInfo>(); + } + catch (UriFormatException ex) + { + _logger.LogError(ex, "The URL configured for the plugin repository manifest URL is not valid: {Manifest}", manifest); + return Array.Empty<PackageInfo>(); + } + catch (NotSupportedException ex) + { + _logger.LogError(ex, "The URL scheme configured for the plugin repository is not supported: {Manifest}", manifest); + return Array.Empty<PackageInfo>(); + } + catch (HttpRequestException ex) + { + _logger.LogError(ex, "An error occurred while accessing the plugin manifest: {Manifest}", manifest); + return Array.Empty<PackageInfo>(); + } + } + + /// <inheritdoc /> + public async Task<IReadOnlyList<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken = default) + { + var result = new List<PackageInfo>(); + foreach (RepositoryInfo repository in _config.Configuration.PluginRepositories) + { + if (repository.Enabled && repository.Url is not null) + { + // Where repositories have the same content, the details from the first is taken. + foreach (var package in await GetPackages(repository.Name ?? "Unnamed Repo", repository.Url, true, cancellationToken).ConfigureAwait(true)) + { + var existing = FilterPackages(result, package.Name, package.Id).FirstOrDefault(); + + // Remove invalid versions from the valid package. + for (var i = package.Versions.Count - 1; i >= 0; i--) + { + var version = package.Versions[i]; + + var plugin = _pluginManager.GetPlugin(package.Id, version.VersionNumber); + if (plugin is not null) + { + await _pluginManager.PopulateManifest(package, version.VersionNumber, plugin.Path, plugin.Manifest.Status).ConfigureAwait(false); + } + + // Remove versions with a target ABI greater than the current application version. + if (Version.TryParse(version.TargetAbi, out var targetAbi) && _applicationHost.ApplicationVersion < targetAbi) + { + package.Versions.RemoveAt(i); + } + } + + // Don't add a package that doesn't have any compatible versions. + if (package.Versions.Count == 0) + { + continue; + } + + if (existing is not null) + { + // Assumption is both lists are ordered, so slot these into the correct place. + MergeSortedList(existing.Versions, package.Versions); + } + else + { + result.Add(package); + } + } + } + } + + return result; + } + + /// <inheritdoc /> + public IEnumerable<PackageInfo> FilterPackages( + IEnumerable<PackageInfo> availablePackages, + string? name = null, + Guid id = default, + Version? specificVersion = null) + { + if (!id.IsEmpty()) + { + availablePackages = availablePackages.Where(x => x.Id.Equals(id)); + } + else if (name is not null) + { + availablePackages = availablePackages.Where(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); + } + + if (specificVersion is not null) + { + availablePackages = availablePackages.Where(x => x.Versions.Any(y => y.VersionNumber.Equals(specificVersion))); + } + + return availablePackages; + } + + /// <inheritdoc /> + public IEnumerable<InstallationInfo> GetCompatibleVersions( + IEnumerable<PackageInfo> availablePackages, + string? name = null, + Guid id = default, + Version? minVersion = null, + Version? specificVersion = null) + { + var package = FilterPackages(availablePackages, name, id, specificVersion).FirstOrDefault(); + + // Package not found in repository + if (package is null) + { + yield break; + } + + var appVer = _applicationHost.ApplicationVersion; + var availableVersions = package.Versions + .Where(x => string.IsNullOrEmpty(x.TargetAbi) || Version.Parse(x.TargetAbi) <= appVer); + + if (specificVersion is not null) + { + availableVersions = availableVersions.Where(x => x.VersionNumber.Equals(specificVersion)); + } + else if (minVersion is not null) + { + availableVersions = availableVersions.Where(x => x.VersionNumber >= minVersion); + } + + foreach (var v in availableVersions.OrderByDescending(x => x.VersionNumber)) + { + yield return new InstallationInfo + { + Changelog = v.Changelog, + Id = package.Id, + Name = package.Name, + Version = v.VersionNumber, + SourceUrl = v.SourceUrl, + Checksum = v.Checksum, + PackageInfo = package + }; + } + } + + /// <inheritdoc /> + public async Task<IEnumerable<InstallationInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default) + { + var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false); + return GetAvailablePluginUpdates(catalog); + } + + /// <inheritdoc /> + public async Task InstallPackage(InstallationInfo package, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(package); + + var innerCancellationTokenSource = new CancellationTokenSource(); + + var tuple = (package, innerCancellationTokenSource); + + // Add it to the in-progress list + lock (_currentInstallationsLock) + { + _currentInstallations.Add(tuple); + } + + using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token); + var linkedToken = linkedTokenSource.Token; + + await _eventManager.PublishAsync(new PluginInstallingEventArgs(package)).ConfigureAwait(false); + + try + { + var isUpdate = await InstallPackageInternal(package, linkedToken).ConfigureAwait(false); + + lock (_currentInstallationsLock) + { + _currentInstallations.Remove(tuple); + } + + _completedInstallationsInternal.Add(package); + + if (isUpdate) + { + await _eventManager.PublishAsync(new PluginUpdatedEventArgs(package)).ConfigureAwait(false); + } + else + { + await _eventManager.PublishAsync(new PluginInstalledEventArgs(package)).ConfigureAwait(false); + } + + _applicationHost.NotifyPendingRestart(); + } + catch (OperationCanceledException) + { + lock (_currentInstallationsLock) + { + _currentInstallations.Remove(tuple); + } + + _logger.LogInformation("Package installation cancelled: {0} {1}", package.Name, package.Version); + + await _eventManager.PublishAsync(new PluginInstallationCancelledEventArgs(package)).ConfigureAwait(false); + + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, "Package installation failed"); + + lock (_currentInstallationsLock) + { + _currentInstallations.Remove(tuple); + } + + await _eventManager.PublishAsync(new InstallationFailedEventArgs + { + InstallationInfo = package, + Exception = ex + }).ConfigureAwait(false); + + throw; + } + finally + { + // Dispose the progress object and remove the installation from the in-progress list + tuple.innerCancellationTokenSource.Dispose(); + } + } + + /// <summary> + /// Uninstalls a plugin. + /// </summary> + /// <param name="plugin">The <see cref="LocalPlugin"/> to uninstall.</param> + public void UninstallPlugin(LocalPlugin plugin) + { + if (plugin is null) + { + return; + } + + if (plugin.Instance?.CanUninstall == false) + { + _logger.LogWarning("Attempt to delete non removable plugin {PluginName}, ignoring request", plugin.Name); + return; + } + + plugin.Instance?.OnUninstalling(); + + // Remove it the quick way for now + _pluginManager.RemovePlugin(plugin); + + _eventManager.Publish(new PluginUninstalledEventArgs(plugin.GetPluginInfo())); + + _applicationHost.NotifyPendingRestart(); + } + + /// <inheritdoc/> + public bool CancelInstallation(Guid id) + { + lock (_currentInstallationsLock) + { + var install = _currentInstallations.Find(x => x.Info.Id.Equals(id)); + if (install == default((InstallationInfo, CancellationTokenSource))) + { + return false; + } + + install.Token.Cancel(); + _currentInstallations.Remove(install); + return true; + } + } + + /// <inheritdoc /> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases unmanaged and optionally managed resources. + /// </summary> + /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources or <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + lock (_currentInstallationsLock) + { + foreach (var (info, token) in _currentInstallations) + { + token.Dispose(); + } + + _currentInstallations.Clear(); + } + } + } + + /// <summary> + /// Merges two sorted lists. + /// </summary> + /// <param name="source">The source <see cref="IList{VersionInfo}"/> instance to merge.</param> + /// <param name="dest">The destination <see cref="IList{VersionInfo}"/> instance to merge with.</param> + private static void MergeSortedList(IList<VersionInfo> source, IList<VersionInfo> dest) + { + int sLength = source.Count - 1; + int dLength = dest.Count; + int s = 0, d = 0; + var sourceVersion = source[0].VersionNumber; + var destVersion = dest[0].VersionNumber; + + while (d < dLength) + { + if (sourceVersion.CompareTo(destVersion) >= 0) + { + if (s < sLength) + { + sourceVersion = source[++s].VersionNumber; + } + else + { + // Append all of destination to the end of source. + while (d < dLength) + { + source.Add(dest[d++]); + } + + break; + } + } + else + { + source.Insert(s++, dest[d++]); + if (d >= dLength) + { + break; + } + + sLength++; + destVersion = dest[d].VersionNumber; + } + } + } + + private IEnumerable<InstallationInfo> GetAvailablePluginUpdates(IReadOnlyList<PackageInfo> pluginCatalog) + { + var plugins = _pluginManager.Plugins; + foreach (var plugin in plugins) + { + // Don't auto update when plugin marked not to, or when it's disabled. + if (plugin.Manifest?.AutoUpdate == false || plugin.Manifest?.Status == PluginStatus.Disabled) + { + continue; + } + + var compatibleVersions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, minVersion: plugin.Version); + var version = compatibleVersions.FirstOrDefault(y => y.Version > plugin.Version); + + if (version is not null && CompletedInstallations.All(x => !x.Id.Equals(version.Id))) + { + yield return version; + } + } + } + + private async Task PerformPackageInstallation(InstallationInfo package, PluginStatus status, CancellationToken cancellationToken) + { + if (!Path.GetExtension(package.SourceUrl.AsSpan()).Equals(".zip", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogError("Only zip packages are supported. {SourceUrl} is not a zip archive.", package.SourceUrl); + return; + } + + // Always override the passed-in target (which is a file) and figure it out again + string targetDir = Path.Combine(_appPaths.PluginsPath, package.Name); + + using var response = await _httpClientFactory.CreateClient(NamedClient.Default) + .GetAsync(new Uri(package.SourceUrl), cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + + // CA5351: Do Not Use Broken Cryptographic Algorithms +#pragma warning disable CA5351 + cancellationToken.ThrowIfCancellationRequested(); + + var hash = Convert.ToHexString(await MD5.HashDataAsync(stream, cancellationToken).ConfigureAwait(false)); + if (!string.Equals(package.Checksum, hash, StringComparison.OrdinalIgnoreCase)) + { + _logger.LogError( + "The checksums didn't match while installing {Package}, expected: {Expected}, got: {Received}", + package.Name, + package.Checksum, + hash); + throw new InvalidDataException("The checksum of the received data doesn't match."); + } + + // Version folder as they cannot be overwritten in Windows. + targetDir += "_" + package.Version; + + if (Directory.Exists(targetDir)) + { + try + { + Directory.Delete(targetDir, true); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch +#pragma warning restore CA1031 // Do not catch general exception types + { + // Ignore any exceptions. + } + } + + stream.Position = 0; + await ZipFile.ExtractToDirectoryAsync(stream, targetDir, true, cancellationToken); + + // Ensure we create one or populate existing ones with missing data. + await _pluginManager.PopulateManifest(package.PackageInfo, package.Version, targetDir, status).ConfigureAwait(false); + + _pluginManager.ImportPluginFrom(targetDir); + } + + private async Task<bool> InstallPackageInternal(InstallationInfo package, CancellationToken cancellationToken) + { + LocalPlugin? plugin = _pluginManager.Plugins.FirstOrDefault(p => p.Id.Equals(package.Id) && p.Version.Equals(package.Version)) + ?? _pluginManager.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase) && p.Version.Equals(package.Version)); + + await PerformPackageInstallation(package, plugin?.Manifest.Status ?? PluginStatus.Active, cancellationToken).ConfigureAwait(false); + _logger.LogInformation("Plugin {Action}: {PluginName} {PluginVersion}", plugin is null ? "installed" : "updated", package.Name, package.Version); + + return plugin is not null; + } + } +} diff --git a/Emby.Server.Implementations/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs b/Emby.Server.Implementations/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs new file mode 100644 index 00000000..925b1351 --- /dev/null +++ b/Emby.Server.Implementations/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// <autogenerated /> +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")] diff --git a/Emby.Server.Implementations/obj/Debug/net10.0/Emby.Server.Implementations.GeneratedMSBuildEditorConfig.editorconfig b/Emby.Server.Implementations/obj/Debug/net10.0/Emby.Server.Implementations.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 00000000..158adafb --- /dev/null +++ b/Emby.Server.Implementations/obj/Debug/net10.0/Emby.Server.Implementations.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,27 @@ +is_global = true +build_property.TargetFramework = net10.0 +build_property.TargetFramework = net10.0 +build_property.TargetFrameworkIdentifier = .NETCoreApp +build_property.TargetFrameworkIdentifier = .NETCoreApp +build_property.TargetFrameworkVersion = v10.0 +build_property.TargetFrameworkVersion = v10.0 +build_property.TargetPlatformMinVersion = +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = Emby.Server.Implementations +build_property.ProjectDir = /srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 10.0 +build_property.EnableCodeStyleSeverity = diff --git a/Emby.Server.Implementations/obj/Debug/net10.0/Emby.Server.Implementations.assets.cache b/Emby.Server.Implementations/obj/Debug/net10.0/Emby.Server.Implementations.assets.cache new file mode 100644 index 0000000000000000000000000000000000000000..7369a24dd860142e7792ab6686e25b5e09396be2 GIT binary patch literal 47770 zcmd5_2bdJcl}45pL>3?v5Qre5$o6ao6us;cRs<myBulb{dS`lf2PSEH7FcXqa*%B~ zTejtF%Q@SZv+sP#`EtJVC0{;YzVqeuzwWB3*ELl&J+rgGe&73gyQb>Bzd~1d^{U?C z{d<R(Em*K%^S7Vr`qF3jzhT*#*FN;!FMn~{b>G^u<H`MBIr*V`w%`7py?5VHdv?Ks zC5ZRti!#&YVvsp~qFf5Xj8~eO3Tl~(pFQbM;k}s8R?A^|vgT#Wxxg>^h1oD4dJ}%B z6y*F$J_<P-RKv`G*E`_#WK=+|Tq}W9Hq0EXGZ_JsVaa4y#ALXVk3xuvmn#%r08sRI z5s3c381&rv135B2?N=*adaSQci!~_K^0nE?s$UFFm#ZhU<!a!m((CQ<dc6K7#ko6T zu}88PAr{8MyhIdc7oN|j26(0xl)`+u6r%D=lliHcs$avaH<2&pf@&rmM8#wz!DLs& z#2YWqRI|a}pcDw^tO+b+sNdNJ>Oy&HDqouN{E4tu^|ORMw4z2cFU*z(yn$qk-L3E+ z5bOlla<aWHVqo9W2H0|CuK4baIXH{-Qik$j=a(M_)jSGNw#ZD6*E?wWzQ^OJvgPrw zc(IU2UCnFUr4knXYE6m@z2Zy(-^X+{@N=j`8J_9&dcFQ$<99Jm3?zuqJD6Y~!#TL6 z0vz?1r`?57n=J&{az%Zh-b^X1`4fczi2CrU4h{~PK~c&Pq?7<ck2=9U`7=SHyIU@> ztiZE)?y#`0jUxbv1tXw)ok;v}wv;^?%m%qaIeRj*1I5snDdZ<I==XP`tqjxe`|0-s z_?=3gI&=D438K#ZGSDi}YS7(ISrcdllV!9*NRcvz@b9kCF@$s8Yru<mUk)PPYeC1{ zyz{k5Kdc2+FY9Ng(b_V7-XJ;xk(EeHzP2ZhG7?AO60d*+l6WPEBwhvD>y((w``K!~ zmdD_TaZ`z+QX8%R#${X$o+M)(h-6#?y03|hFgsJr7pV8H2K4<7dA<EIQ}iv5=ELl< zC`L4Lu=-gK?j&skh@@Q$dLX5=d|2K&G}Pl3I2K3ZIot>tT;`@0WTJl${9@>qIUYyi zGB;~7x8U8~=5r{sxKw9ylWuv(a+6%vb(*Z}@qXA{rqK+_n<|tiGX2!=$;zV&^(&P= zuQxMVt{n<$-d@Dv!nO)wH{eMXcw<vx`RvR<DKms-;`Ma<h03(=^`lP{-vIi$QqHgD z(A8zeMve^};=*nc!fqDAZo#wU%vmJN!=PLW3weBaeyv<>LH5x^LJs9t4azpWyE~Tr zR4M*Es5C`xsgD_p!a475!P~==$}|Jo>CEPdz7o0;%;Y3v>PqhEOB7y@;Myy=_Tjn5 z$rT{O0R0B4s@~nI`iU|T1{`?55HTP`4B~mjEuxyO_*F~+GJQRLgWgbIzsds_b)cF( z9L3|}hJ?87Lfj5K?{|tT1{g}Ke!g@TeO0koE=9d!y*g;3nK_6!yW%(_Q5-IErx1CY z5P3VEkF_S!I}t+eb{foOZCT{rgNR)C9YXk>Lik;+2rtfr`D~o`XaKcl`XEIV1;Bdh zyM^!<3gN?e&bo76Uyb=;1tT?eCZ0c2n=V)LXQK(eoFj{`Aufh`s^PGL9w!<xhIuMp zB)bGET5r&&LUW07>!_$*<oueS8W1%SO2Dy<YFKvTJ)06s#4CK9`lxsm6>A>WPpHN1 zC<z{qBeNRXqsiafLRlEri0-m5tq2*GeHxZAyg%cfmMaZ_0gQ2JWo|YmWW#J>#;fce z4d#BlKRp+i)u0fyY`@TY@g1qB<q!{O5D()06?1`TZ)Mu|XDZ0XR(D9lcQ4+XEWlVz zt3|Jv5w}v&DzTg^+v@qS2J$|<za-_S87MIlV=*X}tFujNSS6F_4o1m%<sQ*+jpIFS zr^<10g-3c=JQYWER6}(P?{7=p<W#<s${dq&#PS6z7D-i(t$Od*06&2Dw%TB#M3UO4 z7NH8Q6YCPWl8J_@7lG3EHL6H*K_!4%tdyP2mrewcvMF_ykJi(U$+W!UAJoe6A-sRE zjfyuwZ11}^;k7k_hc)mo#`_bkf8z>!5$cm>-MFe21@$1M<Hh=jhU`(i&$NLo?_;SD zMvM96^g!>p87h^ABRZ}jdJONW74)p*fqFqgYO{hF16#%V8rTWEx6>U><_kfkT0WJB zPC0B3?jEo>TxVeeoYes5@ZL^$G)<LlQ7bp?j`oSfHjse^auV-TtvAZ)TCI}0``L%c zycteu7^d;=)=3(t4nqB&8iv%bv(7oMAvuBfcB)K3X|HujT(&CXntvKWPPSKE+6F+= zmL%2vthl&#pO6$l?{8zE5KxlaKB|97U853WV!E(fb%P9g5l~6{Cs&Lm{2#X7?km-N z5qi~A*t$&H7ay)C;om@6!&AZgqbc!}F%!VvD0T*hygq}92Fi^3YWiNp#TF<R{kRZa z#gl3=1f5JN`gFe3-{*z1&}J8dQ&_Qu8R(RgYI!C6fhhKP6nl3Ro8zbn95VvPDLfB2 zag>6IB8FyCA$o&Si=y;JJ()wh5dj+@&^WNVKP?2E5rSs%bnB-|!RZ<{^Jrf>Qrq-G zKc%$jq-8s#V(9vsvqIcUgt(XDx!;+uO2MBk<ilDBbqTTumA%XyX$lX^Uk~j*%Cq%^ zkn=Jj=Se*8bjv9h3bRlH4XM(jyd0)r&iN_9`Q?K1EAVu0I8-7Qr0PkskMKvo>6P+I zO~$M6e%y_|3PXb0bTAWkQ#p~w+SvFpQj2&V6iMSkv&{4Nw2=L3Jn0K~4aofkU;@Ep zd$H|7bq4$6ux-GmuBtIAP%U<>Lq8J7<~Uv}a6BV$ybjN*vkc-@d?j0``lr2#nNqG0 z91o7iq5aSW<J+jW9sXgA-**5yro$r#j!-(k2Az052#=rY<8WRt;JiV=c_W_gJ}Chw zs{ND@C@vTMCQbC4@!mp7punT~oo6cxs+B|;G^XHi-lD;IE8g7=Bs}eh(-Sk<lLaU{ zmEN-&<jUF1kVhMDV#K9SVw}Zh`pzT4XgSMqJS%X#4Nq#QZwI;coE97io|=NXeY@AM zJ{kgLL4sh4&#}BiV0lhpc_*Ik<%}E4R6y$Rmf*lLf+KpDhUndRcaQ0AL^JtT0UaCT zc;2Joc`x1{aevOy(CFpxHPeh=Y)&+csPPEujf!ZE`xEaIlI2kPe!RPN(D4wSRx;)= zr+nzBF`(zlr^&=1`T6SdKLGje-Q$`+h3_FG<0o`m#_gY;%u#>pKz<^&kYeNc2Ssk5 z$CJK_4}r#<<y<S~Cg}6CNb1cTJvcf6QM@)k46Za|d>%wnJ_35sDP<-Xco>;vzZ+X_ z$j@Ge(*DmeM6?V|AB7Z>`Y{km{Wxf!TWW0*#v&(6<<qnoNF5#J6ga4(IaZrI7XQYx z_6aa0A)f@1kWYa|okGqI4D@(@tq|{0Y{w|0<dDeWqX)QvPlF)|_zZ{yd=}&`yR!gI zh1Xr2sTJ~=5Y;Bj)gp=z78x=_st9{CGCr63IY=R?p9hiDFM#N8DabvJsr6+$bL&cO zg2d9_;~@H<=GJqVi!qK*363-u>&P68@#b@|d7XPPnSAc$9#XYg)LiCVOg^7;x#e31 zi*|-|CZ7-K=X5A%82C_r9)@s+gb(58eCTF4_|QF%LpGDhhs+KSl}rjBD$#*NR?k<d z1v;C14p&G85&h9#<BFtl9fdE9ZQ<zORl9V1H40s#Ov~tXMbiF@f>x;@VWFjk3L7d4 zU9RH7(uqQrGGioq?}{vi=)cQjo1vuL4*jq&HnX9BFNR?ZJ-aZ{gaQ4xxNcrTZ>wl? zKVbnvp;t77dbHlxe`3}X^wSn|5p#0}inhTRjU<v`0L8dj#b{#NPO;aj*vwdfepzSy zC7Q?4&kTrR`<G(eq8J!rcwBc$(Qj-Loow{UBA?>k<c_QM$0_pl?#S^*HAQ44X965Y ztQP>!p3)yeaW)!+u%wJEpFP+ip9*MXPcT=}<%X|b7}qh(jj`kHVdBbK4O3z!l$#Wd zY4>c=8tQ|W27{2;_>A{1Ndm7C6<cj})prpo@}MzyhR6+|cwL&rL_2CEVv|{f+C-vA z-OVC3$Tt_HeY1z8m<!Uh>q4=wHe#av5c+k!@mH}OKoQKsO#20Pc0xx^5eAI%nkdmJ z;YMRZrP!u8t5h7JbEZ(1#YSZ?Md*rDw)EdhO{Gd<%ed~70=gnyCH=P~)>_g^lwMaP zXNwfHJ{jaC)NmAGrFO7@!nUTe@~4;^68a=v7e_HxC1Q}ui$d2WLTTAkS6b1}8xub_ zs;MZ>>hT7ve+s^)G1xmMjuFuBS3s*%S5m2=pMF|z{>h6pQWW7@O9TXyqKKkwc1MBo zhGK1U$2vwPV++<AH6HZKQl=B2_eEmmPXDje*3}fY^w`loD1>NvOz+DRD_IJ;LM|nf zOHcY~1?b9#sSZ}zu-@;&Z{GvD0`$EU5A=PY?+5(==m$YR1o~mn+Qd{wT6Ief*C7UR z*Z?wd_z{G!VpE^kR$HdtDpKEIN&TY;Ulz}TAQ<XdnXp?V^ehQ~4B<WMqi!<&Ad6a< z3`^!03Fa4DnEyD!S>M2BD`H<wGWrR@h~7<w^F@U7e%x?kO-&#)-ehA(1#jd1llaX% zF_0sDJa%hrk2GQXQ}~@jHVu9C5mw_(raCUjU&8OK(%AgKG(>64gC@*>8ozIGb*k}B zh^#tVX%kl3-7r<%&mjB?tqZ1a14l<Bi^YP)5(|r;MfkQ<J$AHf*k5n_q!J=*%Pfq4 z4&j>{yVF>u*FAw5I-SUpFn?Zf`?7`GR}fBL#0IC14G|XX0J#r{w0J1$OtF6AbtQO{ zZt617JRDeJlBiE4Nz^C0byg`4Dly(y3*PJSbg!Fp^`kU2^vYgWDiaL{l$ezDLJIZC z^KvkW$=cY4td<TWF<G0LEO$ForI+d;5|csW8Pz_Oo%;Y%t7n3kkeh^%oAErCXMq@m zTN#9%&M(_~PKR;p5!`z5bQ>oWT0T6(#0&{B+wq)_Lo-a&P9f?xJm>eY3=?{X5PD}T zLfbkd!-U=~guW2Zw07KVleiOyV;GuU0u7B$ZVhv>ZYIRgz|Nt~Lop1)D8u01h*TIz z=jFu~1+5*1VRH8{xoPtbD<=J-1Tis^V})*LYnWs*4Eq>{3+3<&gLe;um);P)v5dS9 z9eQEVXhKdS2esVue%OWKI>d0bV;(9GkofF8Qj;d876o#x!L^DW19zB#J8y?u7#>>w z$VyDx83ro5iPIzL3n`(=WN7%ImUgVi6L8wI?@cIh6G}*4jrX%+YpabVN}v;mSXe18 zWZ=^G8LCp%>59%AUSZH^MJ$`!g?(s+f#!!+o^Vbb)$qu^5mlUXJFLPGJ;I97y(Mf$ zq;&DAzgg@S)=!ZzB*z(&3+r$S1L-r6?Q|>idnkng%`%|vbSs@djKaVL4BR<6gu-M` zG1=$v@CieZX9(J<n$90OVc-@qaBUQiIN_1HFOZv148f%g!G(Otgh4Jc$Zl&EV<$Sd z!zBy{KU^}OhHp&d<09)-Jl$qhD(kR$o!em&CZ8WB>D(a_Cg8Nl;2AvK`kB@bk1#Q3 zg%~<KG6w@f#_=h^@#T2Bm9Z%gaWLMmWW49qfRM?2TF87gp6-^WZB@+0fRM?jZ4H`m z(zb?Me#o|4nS!Mq141VMSs|a!1h`ck7WoKn>ns4naGnha8ItpAK*(^sN7Tx5c+R5% zA(O}r2;C~S4%qrz<aWsFo^k)MD97jV?2O$zW6tf~=Wg%L7;}4f0H2q;4QUNde|#Hn z72a2az6$yp=<A?wfW8U(1(5q{vgUUlN||fGi~79FK_qi6$RzU@5k3!Fi#Yosk?^fW zLUNS_$uA+?J=&$*dStTs)+5Qj#v=Qd5#HggNrr-NO%jT0Ehv5k;q$U}$*}OPOTx0r zg5_6Rz|zvzCd0zFHVMlX3zlC)ct^KB83w-fNf@rTVEA=}ySGN;$x2IiGs;h7E11*A zdIN|++z2v#tiOS9H;7jEXIV+y1m*;S_GbyiEg%z!-$eMi+`nZI`Ti|I+-8CJTL^b| zq|NVmlyyT!S5z!ybVV{VAXBmYHp1K7Q)W5jd&(rO&m!%25Z<9XA!SDU!ICmM03y+Y zAX7$v7vb*ZRO{Q_tOSO@o?y^+H^JBeGQs#ggwMxzID^8s!wJgm7AW6B`222{Gf;fH zoIu@W0rmT>fNE<yoq^)p=>%%n0_qPCo_2?ct-m_4-OkYN0t#wkv}s9bM?j_~_J;^} zuh*1jgrRMJJDy?W+wp{Pw*})LA>6%|P#D|Zu4lORKpro<y%t=5jPSH&*OBdfhH;;U zam<48+X%l<w)+`;zTHn%zuyA?PY|AdYj6I{0T^^{4nWWkTA=?a!q2<80K?7A1qk=O z7TkY^@OBjcoi`_7;JG;gfxpiJ{?8G9-pmac-Xp-lyO41U-oHS2+QEWtwI=reQ*UP^ zxBMB}qZ-;{7PNnf@OJhU6L33juD~*Kzm|~)EE)MLgtyhkJ8sUvz|$m^x0x4N!2dPE z)Axz<XYRnDb8`p!JRh<^{|>@0Y;y>fsfPiU*T;)3nfe=qpGR{EhM1d6P&GViLHxG} zKM&><3@JCKAf%63kp3OQFD!ElmI)u=cmtWRWa94;-cGkUzvdV$BUvpYIZH<V0paa* zo1Hh;VBiA{{G<i^KO+2`m~${}QyR8u3$}kkcpLT%!qy`71VEnAWeD>c!V?yR|BUc< zs=o8)APoE>4g5(9_<uoo8^znMxd=mesfMs%LHJ#SU&!VpEK5Z#OC?K|{uSZp+}woW zENeI`7M%Zv@cA@HVUQjdkg9mns27613i=x8>!5Fdz6ttwkb4tfDLm$EuEOxuAeRQT z84%$+1v26L4}^EloP_~64aNlE42S^Cf=mGZ6X9<CZ0qJO48~coCm1gQ5sa6DOfdcn z;rpG2vaQ{rN-F^RdjdrAUj`!iPl8PH{~O_RFvnpzehQpO;>$rK@f9GG#Q#CKdjm72 zxek;4O7P^eUuBW~zX+dKa~_82X@Tk0cv5-124pIa|3kR@OYNY!55xCb$fYuS21NK? z2QuNK1HM&f3r$>>s^1rZO9k4xyHz&i*8>*8d;^GJz7b@Cc@bc````r3=;UllXcU*O z<CBoy1PC0`n=O!D&|1kgUWuWVUCO&&CCs;IFyCr{c`;zR8=%GBsvUbTR*cUId~d^( zYWD3Ge3t;9TSsoe*RDMn!}tz?@i~F<ofeE;!028MyD_$ZXRNI4cWFr9Z9%#aNZoTP zH_~?Rl9f2$qv3q71?M8*oJV^zR>$uXh~$*z{T4)*0+D+UsRQ<C4qP9w;93k^ZmXPj z?xdCV^+8cO&*Mp7>4!k3{&xvbbjWIsmBEK0jk?<BK_vYnAd~c^kp7@^kebUKwz4cf z3h9LAV<1BFagYhkGN9><jUOwDPe2$6|0IZne+pz0z8u2mZUe{!e;R^F@Ml0I__H9B z;1x~lz<eQ5y1Q^?9efVb3C-t0gyst%6PlGka}|C&<7^cv7D3`N{4j%Mk~}v}7mOdQ zpD}N|0E}h}mqIxG=_{hk0L%oK!dHP-gVum92i2V56BC-meOq$kP{%$|#qHKXwHt=p z#Y_n<{Z0jb7~)E2>2kV$RhQ>W;A;VZN?OjmuE0A1zY<Tr)TD3%eiev-Uk#e>6!1d6 zG;^lg$BoG`YN`Q+H7wh}t^)`YSPCbw*MJD@deDiEgB==Z5$*=SFu|p8f_p89;BEwU z9xmL1wN|>D0K){A!U^tX5W(F7YBIHq+<pqb5AHS`H-%!p61H5t*8ztKFNG7{>p_Hf zE6A<oYvG#{cxS>w8}0W7ATc4Pa6)_|h!EcdYBD))xr7DcmP`3&U@>8)aKd~Gh%ny@ zdc3pW94xa6-BsL+bgE_4+knP|n!*WnH;7Psp!q22uzzRInHIml46v9mQ#fJn0TJe2 z(0sJ#GcA{LACQ<3Q#c{+2NB`{P@%JJqf*HZ4HSYFi+B(~On@nz01tr(@ODs#0k_wV zcL0b9FohG~oge~y8>quWNIOmVcEB*frEr3K2Z-R_3F`1{qV?8$7eJW6QaFLV8$@7V z2<kN09YZa)++lz)fu(Q)y9-2MM?g(90qKX1qMyy;nxg{zzR+lA=^t|N1~zdi&5H4- z&o4)T$Ap{03HNRg;obx4*zmz|+uDT_^<JPcp{8&`y$?jF$3PvM3YEiXPTWo@-vb;b zycAA&_k#%U0Z_hE9Wmi;t8@<ng$XT%6WT){LVGVL*D17E=<ykwT5hmao!)p41AqxA zg%i;GKm_y%XtKjVRWnEhbsP{(P$`_C9t9E9W1x<fDQf|#fZh)PCZH5fKpy}R&=-L^ z3sm*FsenER04AUmPCy?55zvQ0q{rCmd6|0x_$Hjpmpr;6IQiyfL+k~dptU#sy%?$J z|3~l)kTR@hlkq3~FlY?%4%Z#~jz|4?iYS`oM}_3$cn<24)fK#ou?o{TJ;w4dqH{VJ z;K<_cQns8UUt#+Sp<>wZlg8I)2=8_C-5<<OR{bIzoYspz$-N#ylQAJ=WKE?2Z&#<w z)sq0O*s3s%Q;wmfnho&2;?T_Apmx~Lp7f`Ju^hV@zsbeaYk0!@;c_BhE-j2Nt4`~r zVqxs+mC0lkm^Af`eJGe?N}C{lksG@MoADI4q3HM1FN`loPIpE9LUNPZ-|M;)elZ+q zk|)){A<f%M7hH#VeajE~wdt{vDDw@Nl1MFB(l<=B=NJ2i>4LW|uV<}@N*i%;I7Mmn z8PgSAq`ryA*4@fbLgrSc3l--LT+N{15-IrSAm`{5RdYGB2n9Ce_4Z5bhC6==n7W-^ zIHi%0lJUilQC?IM;jzY?MEpoelJ0EQi!3B1z|Rdu<$i9u$jxd0W)YpXSp7-`8?qU8 zAv~ptec@a-gdBT4-F~4m?R))$LsE`%oz|0yedQpH(QD4FLR4dlq<0+>D!l8U)`3XY zI=P*T@0+J6<aG!2+~f7E7L2J?ATR=rpfJGK2-JOzP$N&KVYH{LT=gvJ>**WxhWg+# zP?ouZ+MB7n^dbI@E`6c~)GLcKzSmQC`$H)$ZhyMA(-(7t$cZJn^asp&F#kZ&$JJ}& z!6Fh4<`QN&tf0pnp-uK-?ntOFfu^o)w?!we%-s)*CjAa|H)SYe5-*2mq3Z=BY7uH1 zfZg@4VKMmWv0yR8BwqZoe#0HDyyLyioJkX7o{Xe$?>Gi2%H6<@^fFW;lEB9ggM@gV z68SC~NH0R9(%7p*3nPAZnkE)JyE`rH-Ki)GX?z~_r{*5}Ss=dbgJ1(@Vi2}`5Vq*V zl{HUmG+r)uH%Fd0zrxd!ycFe{NFPrtC?#)vsum31mTZmBnTRB`=g(<((-JXYOtdwU zoysGWt<9Tq7P*ij&P`oN^)$}_yRQ-)Tk|({XG`q$_F`_Ic28B!+owG(l@{-SRI!>| zh%|4>rc@-u?i=gore#GQw1|g!`Ya~e+nTbGGjecho(~yWubbv#<mHAC)0PXa(WV)Z z5)%C&>&YOwnGY|yXRqINuBu=8`=X+6^!J4o$B@_8cauP?W@UWz7rw!?%4;+Y=!%?R z%gJaOLxR|o*dki?U`->ZcpOFKYOL<NTK6}$uoqKGZ{S)1pQfprM=}DP@JQB$#faB? zjYvV$9PY1-qR0NqFy$Na1}|rvW9Mavz}%Q!&H0p}_NYVEoSQ{!f<8Gl;yHva1NQ|? z2Q#7WU)t#m4YOk8Ll!pW9Euges2fh1ldjV!UvB4T3uB8>*`;!X3GZeTeytW%^Fet0 zR3C9|beLvpObHg!=(L&JG>;X2f&p@RDxZU^`-BS1=?|@I$U}pa#YNRV&Tjf+GwR5D z8fC1B_cSak`n`IEQe(F;L@-_5M=YCrTa(4#oF81X5zD5o*<>Hloa3=EVrX{bCYJ>f z7i@$CMT2MiTSR@aNH!<*+$9{An)r&hh{U|%F~4yHP5O<KpSy~ha0aitl*1R#7Jw>| z(Wg82JqKX;uJ|XObj_ahOSdrgu+Epv!o#}7TODvg^|G-c-$_U5mmGp(Z;?=ftr-i) zij_h>o39mSM+4|sgIw1hzYqqO9tpy7;Z%^j<Y;v!xP;U|15RE1M7}oZqav%GxSAk# zk=N%;WOOj(xs#M@)Ei9|$`i2@yqlfT`L#?3oiUYt8?>u|Pu^+6eCSR1B@~KZ$wwjN zuU5FZGdtRKU)ZE%u$xoFfkO8YAC=N@w@ITL4)>`M#!L<E@<xebP%KwxTLq=~sEg_K zd;Zgwf1sE-&a%3%<I)B%!@aNl&ei}6z<i-RB`aO{q;p&ChDE<xlPN;4I8%UAnQ3V2 zNLeq#g%di1`_2_nzfyNBcb6L<dH>9oE1u*VYJTY!50la2(b-C{NSHP*9w}F550tZh z;gTA@>D6#+T+5R&Te&noGeKYY>PDsR5~aLd+u#M0I#H-wx%PFn(uziEDjES)5L2JB zP^KYedRU<dU8yNK7uQvnSD74s6Qm_d^|(lA85g5hjJ1f%6WYPGq{d1({3ZtL8bhT* zkN>3U>25ffO7XVY7>y)GeFDU@RgtDBV`)uVDx~!kcT<dpg^N3SQYFRp4tA9kK$w@s zCgl>9Hs69#U%rg3)IO|y*|H|F+#z=TEK7`CLYJZ!g9UIRjncntGfFV&HUMX5>c~1M zUfe1pN=#j^VbPc*RTaHv94wM9-&~MxrGdF1ZOebAgJv}<7xrd#ja-6To0*KDXUEJ= zuA0Gahp~iYghV@IXpzvY=S(h@*=&I=e;KC6<u5hn70LDL8pCSzGJU<dxW2Gl5&tJU z;&t|=;U;aoj62;UGx*Gl(dLV`*wYqmYZJ>UxgNVN5o%s!+14BNWz&}C%b>09C}L%E zy*pOCTDjWjbed{nqxi+P0$HgoE|v<X)RcCO=9D_B+2q`Rk(k>rlLtR2&W`$_1lZZ| zg_mln=4Nwca%>;XSA%Q~v%0Hn3|EP&9?Q}6axx!OFNR2moASXrr&i8OER1dEZ%Lnc z8E1cUJzcZ24(seOF%=N!ByMetr0jz*VNlwDu)N+CcWIsF(13xd^~`{Qsfw$Z28C^! zbf;zi2501D!qz@I8{p|t-4^&8n?zqBcndB)mgv?(-rO|7Wl0G`1AV3>ZmZzyQY68u zn;HqXl{e<bWzK8NO(Fr$P4t7YKA0_-)+mkj!J_l}KybH~aksViweHx<;}Q})bk^<~ z&{=D-1#1zie^7}M)CxrZpb~M#2Ni|sJqar`jiug`u$ZDJK}Z8TWfj+qor)>h?HC%U z7d^MRZDK3DrFo)N4eZqC!+&CVOu&z<dp8;7up}!rR;u$#VT-GZyCL%8dWaf7np(@M z5l@S{NW5>YX9okG)s|JU(PAwU?>FjfR41o~5e(@ne<GoZx40gX#Kej=!77P~#fmWD zU1OtKnv0E!6|>nfw7hNvNUPCB*c|GG{&Pzrl<X=StSBReWY3y<L^W$(R}V|t6~dr# TljWzn$zcQls!qjM84><JyXW?L literal 0 HcmV?d00001 diff --git a/Emby.Server.Implementations/obj/Debug/net10.0/Emby.Server.Implementations.csproj.AssemblyReference.cache b/Emby.Server.Implementations/obj/Debug/net10.0/Emby.Server.Implementations.csproj.AssemblyReference.cache new file mode 100644 index 0000000000000000000000000000000000000000..161c604de0ddaa77c17e685646b47492f96bcd7a GIT binary patch literal 20506 zcmeHP2{=@3*mf#wiBeQ3ZHh8xA5&45ln7ZWNz9x%7{&}ULn&I6QYcYrQCZp~5v5&4 zD+*;vU$m;E^%H6JpL1r$87<SaT>pPv{oh;{GtT?GX71;HmiK+0_ly#NS5Qy@oJ{?B zL6GVE03H_-nBushevr^Km;nYd{NTSCf^aSv2!%r|4i5}8HN)X?L{ko%Y08C!bR6E) z5*fEg#vR~s9E-yN0QkQu5MT9*^>0uC0HAkQ5kLhQh9BKTPYS3w{2EvS|LU<6egQu# z0IA6D5uz&_;Fn5>Jp8wdt0h692O<S1EW3L@<WuqT5MXKb)>9J&#%qPPc?P%sW$s&c zBBQw6gnH=!wc!zNR-65Tob3bDTUgE!aURF4WB(a<0QZU<lJ<Ojx3Nd`uJ+fZtWxni zpG>z%RB8UVf@lz<x@3Ly!-_mz+qPjUZyJsasa0OAz9r|qmQ}e@S!8zTs&=0fDJ~NO z_o7%X);IMtI-y|-%;*Yqtieo(FJSYy=BUze_^}4op&Vf-A2R1cp+Y``GuFU4l*wTu z>T}}-LR@o@%3?uu0+~o>GVs1cUql7S1DGh2BPNT@_2rr3aqt^SxS_%?t!(Vz>li0X zDsMPi-$po55)Gl6mhd5ZE79++T!<Ml7fr%&p>HS;E^a7?Bl-7$yKF3Mp)W%qg!nj+ z0s6DKex^hm1xM}zi!HPjiCBw7FoZNc%anq81YsYmFQov)fE3{$*m%&#Zx~+dY&(Ky zx9X8q%D{`po6Q+J0%nBSJjt1uHpBAiQN^`kg-h2`%U{GVR@R+!fOdM`<cr}UK88!* zo+%27+i@z>>&AG;i8{NAVgbXoj}B>OFCMCtweFMNq6hs5i7pY1ZFKrP+bk7mz>{Uc z9BxzdrdNjJG8|{Pl+StAHmeH!T+S>(4G$^~Jipe_tUKWL6;_NRRD1GpM5TZvDib7| zL1c(zh9`-YqyS8j$rp>w0Qqbo8x)w5aRiJgBsSP|j$sV#i5&OF1&s@8*f4-lg3uwM zrt{6a#00S&T4ED1oamd&gR_e*&iDZ~0T?P|a|Ae$$A@75B2aMzlEf4#C_(|~j!Ia= z5*W&yD=XiI)uGYPhAl0E+MK2Yo)|UqXIGB{Qju#TWJkX#&Chg<Hxo`8d~TS3FLJ>o z!>7UcT19W;yBotU=`}xId?w2}Xz6S<r_vLAs}9C#mx7h8pSN3nj=1sYtmm3l+_DzW zHMJKW><PBoSUa?*Ry=>{p0|cEmRq6vg)95k__P0g-1kC+<;N=nxemEO8*e^Z(6(tY zM2@5NQ2emCIUw!E6VwR3cOTX)OJS}CT)G0qm_gNsmxmgPFN4A)6YwBN2dP9eI-&#Q z0nCz<Cbob_r&95<nX(fJVPu_t6)z^M4~9W3_4o4S{uS2$6UqcaKEwzT$fnFyB!p4s z?ziG=%EVItFDMgZ{iBr0UI?<_I3@~)p)6n7bh)#9F{&`{QpYR~miT*Vn$ZR4AEn4y zb|B;l;Z-@#kHce{lHg?AnW~WV#Rv{2;s~ZTJfR~jGX<8xIBJed{jdy8S3RZ1MGJXR z2o}X*@#|m6RZXE$D!W`AH>=#XTR79|#l+-5MF{i^iQ2F!FgA!tDQ>vgI)^kyr}+8` zlEqfb*(uzRf=G^i-0~jvsgXweeez?SCPrtL9x2?juKwADSL;f*5~6k~X_6P8s#m)> zen0zg>7wYH0|8FJhjj;zMTG9w_e@Vc;MreiN>awL<&E|atI%9k5O+D|a2v4_aFjxc z@k4<3Hge@DCe;iC&CJZ0WM3+Y$RbEo1jwd`4Th4rrc^i|;_xO6POv`%N1{+A%L##y z!DTV{EF!$Du(Nh2J7V;NugAyKqVy=DBftd7J4y(VJa<_x$+d{691Zwv%a5?SvVWv? zp}{Ujm62O3z+9fVTeq6;mb>?y{$g*hW$p!~g0XrE3j)eUM|I2HpfP;mwB!wIXaylO zEry9sNwLPeQ<@8gllPij8*X^X-+x}j{VZ2Xjoz?F!=^2IQN1Sr>WSfp;}@SSoV(#v z!ffu^gOs&;>%CM=sqYx~QDR;midgH^+ejC%L-Aphz{--76UdiIpn>q>)fW#D=>(=k zLjYH%%b=X#;LR9Uz+uC?4u+7&{|SoRx=h3vyU{`vGuBVUWmmz!m%!QIg8N7Ll)V*Z z`*B4}btKk_%M{42*bcpp5kzv`NZ%{k3X6u&bR;QBAjX4UK%RfzryBbdGFnR)Zh75q zejt@vVKk`9|HzT3PrF<`zv%ezc(u(K>ha~{YtptYx2VW#?{O~e`o5)iM)j^Ttq6;q zeqg=VnNzKcH-!jk?^hacxalx>_av>RW0Pj-@{W+FY+9&lIV&K1>h4z>8GB1QE*Dfz zy=Zsf?XJMQyRmbIAKJFGd@S|NCDf-P6xUDEw)BaHeG0{nkpNe4<co7O5Kn=t5qJWX zOrYY;D4jk<m+4ahL@vB(hy7MU08)#XMwAL@8mfW2L;<Zkd>!4GOa3?V{2Qv@KCm`% za$F`i^U9j3tH=9y@7DKue7{YbZ?PJyg39PMXUW9NaV91`?k4meSPhtEWt|ng_{h0F zc;~;PM*zDH_j`2Za%OGY)a%jb(%xh=%^c*ttlJZV)%T1B&QCkwDtK4DeVB^<E^3+i z63!Hzp>F5TJZ65%{rs$G<Ru4H{6CKOuBW1^Z`m-VCOMOx1K7goFukKXk!K&|OQMs> zG!~UkW6-H|237{Lm8o~w+yxN43x^X-XNeBWDyTaMr1q#q^G{GB&{~NiIuqT5BkJ!3 z{~&b|$)l?Wh&SQp=o}23=xuS79~Zg0Y`|S)BjSU>488#3n-cLv3XV!7VMZm5%z+O& zqjH#o$0ifNJQSU3=P1ymtf0V?c5*SqZz7!BIS)gLeGGYc?1FTrOr$}Okiq0L*xZG1 ztsy9g$3?3togs{Xc++%P#!4h(jmltXH>DI`nuAtV`g|qTSzPJl^P8vv14J?6l0!fE zb(exGT(Uq4uJ$e+vyL|Xs~>E&AZAp1@iwpXyksb%==|wd$B0HR1IMo~6fV>4Kg96n zm_*0qhVqa9D8)DRQrqO5sB~_tb^7(BVdUAWp3rN;^58ID)0xcJNT-u0mU%bo4yKGS zKX&q!8F|$fYTf3S_ct~0-!-O>)mm^zF%F-oXP4rvyL)zkQ;l~=W8YzO!tW`$Ie~>8 zrHc=-9j1+lYM6TWv2)4$4X(G-lR2dH3XYcQ=w~L)-C5VG9GmAkA18gDkUo7OZr}Up z2g9u$6sB@#cAISRBq8P#c*ZH=uJPvC3jV#^#yXZ7u*NMNS*K9SdUt9~ONJ+uU)jHS z@mtkK_u5bDmtR&sz&T&78XTK<JFTtWr+2QwQ^FagQ+ihGlG{>pcw5t=6?3OnHso54 zUt!%r&X_v0q-<DJ-=3L{x?Y8W+4%#NE4;v>^C2l&TaBS-2^tz3F0HcF-lWs9`c%}v z4Y8Rj*VCiTxLd5A#1f)3n{+<DuT3|(?n!A*T|f)CZ13jX5_6QAIXK<8;G&O8s!f!I z)mrLf+ItJ(>QkCesuz{{tFLNu$}LGEOunNG-kpD&eMnF5wB5^NPRk!wjVfqb8Sgys z#LIZ|oKJmhDm>>W8Ei4kRd%?NrqR+nD#yTtm6hHjvPrq5_0Ck>uI&oR20gaXoL!2W zS*!MJIDF=CQJod9#v<f!(|$C(74|JHFglAL3b=h`YnZ1X^*GpQe;f{&5P=9XnN%Vj z#FOwuY%%Tga}I+G5WrqTLvouW?|jE=ron=k>v%p+sS~-5_E&Ilh$sLDjm~BT;yT|+ zPyhMPfBy4x{^Rj&gTtH%wdVgFCkhG`ut8A<LYsWwO+C{QL2(-D?DiFpaH#S@^N+L> zNr|F?192Y7<3A_*FA4pTBqZ%bQU-@P5z>AAy-Wnk&O~gG&lB)`g*X;N$iN9gkV1l} zV}v9itO|$hL#KBN+aSWOBH>Oyy3t*0!d%pk`hQarP!s!4ck%Og+$a?p{z3bwWt>qD zr_9<(SMrCBOKOZ*ahQMEvFDoJgC4H+%WQ9J*zUc=+~vUVhifhj0AAnTs8*PupE2Ed z$N2sK^c$peX>rR1sD56fqW$`E@Z^MmV-xvV<8qqJHHwSKv0+X*xrfdQ%p3c89L%zv z-F?NG?ZWf-m$8msy+5UW{R!pOdfQhSUq6Y4gzV$tW=8b~eE^Sd;>B1LsZyR;$Y3$) zG&;$QNN2Ga5EVJ&fIa}8oHRmQAzK*k%Vz{Z^LhM0wD;GQNzuZhlgi2H1Y603uC2f# z0|POo`WVD-%=mk$Wyr++M=4$n{5r=t1`}>ZF+d@FWJ6&3(}Y_}&Bj)jLnihXvu`X@ z3pnZNf3GQU<yXQVwTtNAu?s%LVIW4q;QYK*xOC3NY{GcIk=Q0i%T@<A`R}y|j=U=P zqxKN>JNEGX3@=As_%h?K=rvYs3?pP~LNxL3H3WZo;Qvu0h?F;1ED%Cm<RG5_$AW?( zE(_v<;XynW^rLuy&9mv8HyxRW#e!Xr-%v#hK56>DJF$~~WJQCl0sorypEUj7h5rTp z@Y?#v^`-AF2j|I2KM$sy5zICv;HWs_Hwx?0i+U$m)-srl@tX^fsQ1u5Qy<K}#ZqX0 zT?r;p8(|ZE{05mDBMvWLmAAXqY3_qW_t)zTZy3|JsOLqzSw+jac#0XiEpyYc^fLva zRS$NWD?K|hM<-Ty65)2<UcdWW=HHCFwsM-f_L+0}g(rL8*?B+uwMNI;ecnkmtoMpJ z%I`DwUvHc|z~sz=#S?FgnRy`7>vhKY$6n90r%fE>0GTK20v*LW^3S7-`H8cvH-KTU ztN{0~uu3t9s!^VWJjs_rBg3r_Dv3t*#gmC5CBbS6l#{vnY%Yn26NJOZNrRv;cv&wn zg^wk6J->{7(pjjqD=Ix1mBwfcK0Ad$MY{=lvL*nklFul9{tZU$SDr6j;I@r?qF~VY z0SfoWe?F4`jIJQ;(Vx2aL22C*?;hnE(IM{p64&lGYW~!x{ABj{y}dv0J)JR(6A({R zbGXs{ZNI63+*89mqW73h8O&`BDNEeNn4oa%JyfH2?QUsf3y8mxI>d6h`%c}LBg%|) zF4k#1*lkG~r>|I7czujnDavTsBCU?ACiOD_Z&|Q0auM~)wbuqg8k0cs^#xf(7K=r} zs=f{~x#dF4AUNuxwPlK=%!_q`@TQKFVF6s%9hgOTXm1lro*FicI-i7|A9lMev56R1 z@yGaa2<p-YlHKv*Li2@iqCu(&Xg!+%cf7DewMZk?5tHaz$R=CwdHV*1squvjp#JB~ zvXyaXR0OA|T_iJscv$ml`Dm_dgyNJDZ#z=8ttYpQv0xb()$Z$6JY)T8cVgVM+iQBn z41bxBTW<C=GPl;K<lh2P@rgo}TmrE9oXWtB=Ms!g`OQcYI%aJbOg^@UKl_<ZW#4P3 zPv)gBn;LoT>4)vh_a|u(H76G88}+gByda#^P1M`QXFEO0+jH<a;Q9qpDRL0S$z_Tp zJcC8{g{Vv-gUWy)mPA*8y-ZUK<}kuJY=KY!AGv}p67F|M>=CK+I7>zyC%a)`#PM!) zjXIhQdPxhQw%7xHUx__-euOS(4@AR&we(aez{p3CC#-g{MswL1c^pnSTn?t<I-LzM zL1*MLhRW;d`gmisXr+jb$Xn}8MPUA*NY!6u-~&6SWW#aePFJp-*K10yBd2$R&7KST z&POI!b-&geagtaWfAUnle$2keMFNAszJd41xjre>{S)%_uRmN&Cnjl*Ry|g`WS}V_ zhOx%#(TsDBXB698j|ODwT5DV7k&~ZC6?lQ+dbcw4t)kLvO0$YTD^D&cVE3|&$ZAU? zr=VCaXLJ;|w~s6XrlR;Toy%QX1kD&oup{}J`I-?aAlON;?8EiJsG`vVR2LFZ3q!}c zMmmfMH4#V1@ZU?vbPVL5<iuOHPw?TprG)-asK5mAeB_)ZQR4a1naH_Q4hJ8&bQPcO z!@S1PS+Ap{2DwE*``e;`q{iE6EaDekPb&ZhNYoI!Q9t-~m%crGa}UzDx6AJtSazA9 z7w?^-5a8jT>wznYoil9h4mIPmMmS?_Tn52jV~b@+_RfcDlR_Gddvcwek9JGE?z2qM zY15qm#m0SGLtNvX8%o-VroHMF)~@YWeSg$pPMqeRvS>B0CC%@$=V^Rc9`a<8aJqR# zX$*jS)Gr}*yTXzzqmNo2dODaMt~E>?KGHU1jKz+T>A(Zk!Yj&|>6VJy4PQqEke1|d z7iuS%7SD^%dG@kp&QzvxvUbi&tu=#`99C`P(=+WnS7>LoO<V=;(P}Kj1!_&QIPOyH zqmsVNc}&selniRvh}>|UzO72aFpbhI-29CnIIg*V-W$~W<KEb1Yub*#=CQXL^?`Kj zre%vVt33dZZz~+LA-Qu0gGeSpbTa~t$)K|+6zq-#*-O;mPTN3i2**Ot?eat6GBSK4 z9$7k<AmR24Y0=qL7m+k(Pw#YYQSYF+MNifQ0Le3Q=Nea8{|eI=`jLM+_wZ%<nW)MV z408C4`8Z}Mm&Jj+A#c%D5`4Z4UVU|DTxq{^o#6oYU!1KST#$JTA$+Kl4GFx%h?uJM zE*Ir`p(~|<-<3uWr6Hcx6aKM)+b)va{Dt?W!ow2p``&Gel`Rz>Bh{5+4h(v@`N8b% zwqE6K<6IcV8y=+GdHWnXe!}C^z2WN&jn4%b_oQeK8&!X+<m0E!XSK>B*AK|6oY6L_ zq|Bq~8hQ7Va@&Tyf<Er+OH`_pvr7gmWE?l)spZ{Bei*rFh}8rwb;r^%i;4!3P6^dl zgmv8PH`j2MAi4bPl0?+{qYR&VwJa)W0(`!U86#ArUL281B>s4LYepneAqIuzOQJDB z2820*>>YL&g3%l&C#i@mMrM5UIx(4jx&T9@{v?UrN0Pg(C%H_2RJr%y?vY1Ia`WdU zrf+*wq&Y7%YJ;M2Nl9Iuy}K3Hz4(ESH<(6#ykueO=UCG1$D!TxMo$`e`;F>3oh|7e z%YEZ1%?Vn;7YAR=jWyi5qVVG8E9yCmyyvAI2_J2p`O!jEHTUD~kIEZ^mh0_FZr46M zsaKJ`j@Gu=(<q7kV(yMES$Kp3%>D}NFOo+fo0&m)q8Z4bF_;t*cE<B3yb}%*VQdzB z%jWm|6NzG7KJeEwFhaK-*d!)&$PecHNqSdBSg#4yPaQv7N6#T(gpsM^-M!=i`#k&m z9-M!`+`#UBW<>S6w9`r!S?8WU?hb4}$cj^J@!2R)*jj3E=UtLvOjTMFE-R<-gLz@! zI$X&NziggiLRfiFR_OTy7j#pVUHP>h<&{e|Hji|erm)$mqJ=)<Tw-g<vU!%Rqt299 z_PK@9n}XLbSXptpFW~zF<iAKZ9VFoiEco^XDhtnK5#+6?2<9^c{>)G?kOScuf?zmh zp)SaWSUk{_ibHPolsandDkW_x2!<Cw)`-KpKsG!OV|n+noyyNe=}OiF0I82jS5<x^ z3@@|RF7op}G+}H%lZ4Msu49MU97+SBzAtxD)XI;09Z~FcQ(${LT@a84q`CLMG{@y_ zXk9sf=B;__jX(O$U1u_GOAPPInf5TXkno2Q7C966qkSGxTNboWVy$lGA7CG^*K^OP zQ6hwUs(iX1Fc#eUTzh2qL3_417@fOhn;rP#!5x%h7hm&BNs9*e2fSs$#`s0mD^F^r z5UErW3xqr0aAgG|V2&)i?fo5Y;mdx0@VQ$Wj)X055u(5Iso~3rJjQRrNdNZ`nqvpc zo`N2)LzcRdbGg64aL=U`-CkKQ+;G0FHu9s<HMcvOliEOGX~?i=UbpfS76yIx*{a-8 zwQcJj!H@^-4b%4~Z*SOZu_0yU%a8WYCfcSHa~H(5##x;&44wCB(5Ee&lH;%bA-f+V z58dh8uR`OlWv^o8^Ut0WHEO)eoQG(6U7*!gY%kL{o@Fwkee%|owmUAM3^Sofp)esd z31G+uj&Y1CSssq5L@I^GqLFE2I-W+MWA|u>%3exId8Cy83PhawLCCSmpQ3pRBz1tY z{a}cg>7lX#!?XS**?qK*Sr=@cTN6G+ap~ra*SxF!<5nFX)8R3<Oy7L{N{!yj&&EHl zG-7Qn&cC&U!nv8Q4mDo0-(y<y`Q;=lo@bL;Ls_p#n;6~wm)xz(>rzxQHT#cS_T2c9 zagTft`zzIXEwctJDL?h9x6<xgYQZjZGBQJ+MLbmRTj|Dc{q$sz-n&OA**B+e`2bK{ z)d1EHvHt?!EHg5TPWNTv2_y;yu2UgvP2>RxeuM8&_RrGoZio5x0YR9UZ3Eee-R=G) z*-g(AHKJ<Fu3H)8E_fR<Vk~pw`7&zo8`tXvv7Fhgu%pX30b7$+651*oIGgnI-zTYz zI%C_g@}9D1M7LdKeKb=en$HSdPFXxy`r*U=2FKBRpFVqkS9R-!F|$UpG{y}*=WbQM zYjCu{GBEIIGcnd*&AiHluxe!NSaqwbFQ)6qQz}rht%z6FkIFA{1Hd0a{{^neL=q9C z(+NZxlLQheWD(Oa*Iu%@M*9^w7Q9P>zbr#?^T1ba4pCbJXCoH-^8SH4ACI0E^*8!| zQ}XX^Y|Q>9UW~ur?`X(AJt+7o{+#*jFa~_O5bjm6c=H8cG&(*j<%wC;6{Gr=yu4gW ze@g{YFhCxt8ysCYcjiFy-POQ68SnB78f4OF3<^l066ho{gqQmI2if1!A!PU=_agZ- z_`z^l0507~7NgQt0XK%91DolJ2w|}-F`~XO<Q7tb>nSyk?kc2fZ@;)8=>zNQUPQ?J zKtDk6Pf5*i9esbZ=%rdX-tDQa!Jh7K!|5H<OaqQYM;77Ak5WqdWJF%9bkvQTc|z?? z;_`WUuN>kkD7UKbsTXIbMiy`6X*rg^AEa|u_eJgf*E^@w4PZ3v==srN9QAbnadpY( zH<zlas@^t9G`JpdR%469<Ckxb5hB7wUr_W9vw4^8>La;;qZCSvA5>@ZtUln40})~} z$YiP+ok?M02P^GlMkrwr3;vQL=J&WEn7U23VZ!8KyxcQr`qYB19hJpnC?}`|e(|eo zHAYmV0W8F`q%q3b4C>Mx@sNotlm+47t&qgZ;2x0FttXPJ$R&4J5Z(p3epLcC*_o%L zUyInE#uqZ3x8DDOMR8*{J~=7u+Spn@XePbKw5;51<aNzyInQlUoF24wza%KF$~G|H z>72<M(q5DqHF`!tOR27-Dw$7)^s4K^0@iGCiYd-qP_H%Nv1U`!<7?p`NV?857kj0@ z2qwFy3ftp7uGa0yB06Ok{B!Nz=&;P(fXruC>%ucz_v>t#yLbqA6pb006ApWy<s>`= zT)sexafIA21gq=E^(F$I1Tx7KUl30P>39gslv8E%CG^FGas#=%`N-|5NJ#>*N|-^U zo8m6?br+3cX6VroJE0miLp5ypD@**v=zvt^joJ=3yuSqc^0iml_Ov<5*s*rtu0C(0 zM<r>0I%vv<p1+@F+r7u<SjXB;)sz6eVKWHFRl=9ob!^1nNm71#C^F`%lcwS>Zyv>k zJh`$%Fq~+L_bLCV9$ZnXYaeN=qFb0vSYbD6e|D~OEbEL>d}CJQ)43(FiFUm<=`IS@ zDk{oEnMoKL@i?96Qw2=NKw_l8gvi%LrNg&w(LjjCfR`~;JPRYmMkXl>$z(i^A>@e8 z%hTYRF2)N&k)@jhM#z=-d+mMEMq_vQI=Z}+E)7UR!#88XICusIi-r^}<gvA%hzAQ{ zb3y`wNl>Ve!-m_#LSG&~2o4MI*Q7~I4GRkdQ;9r=Z_Aw#1B=m`h>B#eGZ1|FEp6Wf PIZ-kVLyErr&m#RFu&|mS literal 0 HcmV?d00001 diff --git a/Emby.Server.Implementations/obj/Emby.Server.Implementations.csproj.nuget.dgspec.json b/Emby.Server.Implementations/obj/Emby.Server.Implementations.csproj.nuget.dgspec.json new file mode 100644 index 00000000..30133309 --- /dev/null +++ b/Emby.Server.Implementations/obj/Emby.Server.Implementations.csproj.nuget.dgspec.json @@ -0,0 +1,10134 @@ +{ + "format": 1, + "restore": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/Emby.Server.Implementations.csproj": {} + }, + "projects": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj", + "projectName": "Jellyfin.Naming", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj", + "projectName": "Emby.Photos", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "TagLibSharp": { + "target": "Package", + "version": "[2.3.0, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/Emby.Server.Implementations.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/Emby.Server.Implementations.csproj", + "projectName": "Emby.Server.Implementations", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/Emby.Server.Implementations.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Providers/MediaBrowser.Providers.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Providers/MediaBrowser.Providers.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "BitFaster.Caching": { + "target": "Package", + "version": "[2.5.4, )", + "versionCentrallyManaged": true + }, + "DiscUtils.Udf": { + "target": "Package", + "version": "[0.16.13, )", + "versionCentrallyManaged": true + }, + "DotNet.Glob": { + "target": "Package", + "version": "[3.1.3, )", + "versionCentrallyManaged": true + }, + "Ignore": { + "target": "Package", + "version": "[0.2.1, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Data.Sqlite": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Relational": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Caching.Memory": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.DependencyInjection": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "prometheus-net.DotNetRuntime": { + "target": "Package", + "version": "[4.4.1, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj", + "projectName": "Jellyfin.Api", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.AspNetCore.Authorization": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Http": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "Swashbuckle.AspNetCore": { + "target": "Package", + "version": "[7.3.2, )", + "versionCentrallyManaged": true + }, + "Swashbuckle.AspNetCore.ReDoc": { + "target": "Package", + "version": "[6.9.0, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "projectName": "Jellyfin.Data", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Logging": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj", + "projectName": "Jellyfin.Server.Implementations", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "AsyncKeyedLock": { + "target": "Package", + "version": "[8.0.2, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Relational": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj", + "projectName": "Jellyfin.Common", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.AspNetCore": "(,10.0.32767]", + "Microsoft.AspNetCore.Antiforgery": "(,10.0.32767]", + "Microsoft.AspNetCore.App": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.BearerToken": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Cookies": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.OAuth": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization.Policy": "(,10.0.32767]", + "Microsoft.AspNetCore.Components": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Endpoints": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Forms": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Server": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Web": "(,10.0.32767]", + "Microsoft.AspNetCore.Connections.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.CookiePolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.Internal": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.AspNetCore.HostFiltering": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Html.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Features": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Results": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpLogging": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpOverrides": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpsPolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Identity": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Metadata": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ApiExplorer": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.DataAnnotations": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Xml": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.RazorPages": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.TagHelpers": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ViewFeatures": "(,10.0.32767]", + "Microsoft.AspNetCore.OutputCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.RateLimiting": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor.Runtime": "(,10.0.32767]", + "Microsoft.AspNetCore.RequestDecompression": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCompression": "(,10.0.32767]", + "Microsoft.AspNetCore.Rewrite": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.HttpSys": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IIS": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IISIntegration": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets": "(,10.0.32767]", + "Microsoft.AspNetCore.Session": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticAssets": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticFiles": "(,10.0.32767]", + "Microsoft.AspNetCore.WebSockets": "(,10.0.32767]", + "Microsoft.AspNetCore.WebUtilities": "(,10.0.32767]", + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.Extensions.Caching.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Caching.Memory": "(,10.0.32767]", + "Microsoft.Extensions.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Binder": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.CommandLine": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.FileExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Ini": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Json": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.KeyPerFile": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.UserSecrets": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Xml": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Features": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Composite": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Physical": "(,10.0.32767]", + "Microsoft.Extensions.FileSystemGlobbing": "(,10.0.32767]", + "Microsoft.Extensions.Hosting": "(,10.0.32767]", + "Microsoft.Extensions.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Http": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Core": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Stores": "(,10.0.32767]", + "Microsoft.Extensions.Localization": "(,10.0.32767]", + "Microsoft.Extensions.Localization.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Console": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Debug": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventLog": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventSource": "(,10.0.32767]", + "Microsoft.Extensions.Logging.TraceSource": "(,10.0.32767]", + "Microsoft.Extensions.ObjectPool": "(,10.0.32767]", + "Microsoft.Extensions.Options": "(,10.0.32767]", + "Microsoft.Extensions.Options.ConfigurationExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Options.DataAnnotations": "(,10.0.32767]", + "Microsoft.Extensions.Primitives": "(,10.0.32767]", + "Microsoft.Extensions.Validation": "(,10.0.32767]", + "Microsoft.Extensions.WebEncoders": "(,10.0.32767]", + "Microsoft.JSInterop": "(,10.0.32767]", + "Microsoft.Net.Http.Headers": "(,10.0.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.EventLog": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Cbor": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Cryptography.Xml": "(,10.0.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.RateLimiting": "(,10.0.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj", + "projectName": "Jellyfin.Controller", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "BitFaster.Caching": { + "target": "Package", + "version": "[2.5.4, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Configuration.Binder": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj", + "projectName": "MediaBrowser.LocalMetadata", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.LocalMetadata/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj", + "projectName": "MediaBrowser.MediaEncoding", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "AsyncKeyedLock": { + "target": "Package", + "version": "[8.0.2, )", + "versionCentrallyManaged": true + }, + "BDInfo": { + "target": "Package", + "version": "[0.8.0, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Http": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "UTF.Unknown": { + "target": "Package", + "version": "[2.6.0, )", + "versionCentrallyManaged": true + }, + "libse": { + "target": "Package", + "version": "[4.0.12, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj", + "projectName": "Jellyfin.Model", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "MimeTypes": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[2.5.2, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.AspNetCore": "(,10.0.32767]", + "Microsoft.AspNetCore.Antiforgery": "(,10.0.32767]", + "Microsoft.AspNetCore.App": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.BearerToken": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Cookies": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.OAuth": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization.Policy": "(,10.0.32767]", + "Microsoft.AspNetCore.Components": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Endpoints": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Forms": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Server": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Web": "(,10.0.32767]", + "Microsoft.AspNetCore.Connections.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.CookiePolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.Internal": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.AspNetCore.HostFiltering": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Html.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Features": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Results": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpLogging": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpOverrides": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpsPolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Identity": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Metadata": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ApiExplorer": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.DataAnnotations": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Xml": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.RazorPages": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.TagHelpers": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ViewFeatures": "(,10.0.32767]", + "Microsoft.AspNetCore.OutputCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.RateLimiting": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor.Runtime": "(,10.0.32767]", + "Microsoft.AspNetCore.RequestDecompression": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCompression": "(,10.0.32767]", + "Microsoft.AspNetCore.Rewrite": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.HttpSys": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IIS": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IISIntegration": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets": "(,10.0.32767]", + "Microsoft.AspNetCore.Session": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticAssets": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticFiles": "(,10.0.32767]", + "Microsoft.AspNetCore.WebSockets": "(,10.0.32767]", + "Microsoft.AspNetCore.WebUtilities": "(,10.0.32767]", + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.Extensions.Caching.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Caching.Memory": "(,10.0.32767]", + "Microsoft.Extensions.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Binder": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.CommandLine": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.FileExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Ini": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Json": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.KeyPerFile": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.UserSecrets": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Xml": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Features": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Composite": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Physical": "(,10.0.32767]", + "Microsoft.Extensions.FileSystemGlobbing": "(,10.0.32767]", + "Microsoft.Extensions.Hosting": "(,10.0.32767]", + "Microsoft.Extensions.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Http": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Core": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Stores": "(,10.0.32767]", + "Microsoft.Extensions.Localization": "(,10.0.32767]", + "Microsoft.Extensions.Localization.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Console": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Debug": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventLog": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventSource": "(,10.0.32767]", + "Microsoft.Extensions.Logging.TraceSource": "(,10.0.32767]", + "Microsoft.Extensions.ObjectPool": "(,10.0.32767]", + "Microsoft.Extensions.Options": "(,10.0.32767]", + "Microsoft.Extensions.Options.ConfigurationExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Options.DataAnnotations": "(,10.0.32767]", + "Microsoft.Extensions.Primitives": "(,10.0.32767]", + "Microsoft.Extensions.Validation": "(,10.0.32767]", + "Microsoft.Extensions.WebEncoders": "(,10.0.32767]", + "Microsoft.JSInterop": "(,10.0.32767]", + "Microsoft.Net.Http.Headers": "(,10.0.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.EventLog": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Cbor": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Cryptography.Xml": "(,10.0.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.RateLimiting": "(,10.0.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Providers/MediaBrowser.Providers.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Providers/MediaBrowser.Providers.csproj", + "projectName": "MediaBrowser.Providers", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Providers/MediaBrowser.Providers.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Providers/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "AsyncKeyedLock": { + "target": "Package", + "version": "[8.0.2, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "LrcParser": { + "target": "Package", + "version": "[2025.623.0, )", + "versionCentrallyManaged": true + }, + "MetaBrainz.MusicBrainz": { + "target": "Package", + "version": "[8.0.1, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Caching.Abstractions": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Http": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Newtonsoft.Json": { + "target": "Package", + "version": "[13.0.4, )", + "versionCentrallyManaged": true + }, + "PlaylistsNET": { + "target": "Package", + "version": "[1.4.1, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "TMDbLib": { + "target": "Package", + "version": "[2.3.0, )", + "versionCentrallyManaged": true + }, + "z440.atl.core": { + "target": "Package", + "version": "[7.11.0, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj", + "projectName": "MediaBrowser.XbmcMetadata", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.XbmcMetadata/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "projectName": "Jellyfin.CodeAnalysis", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "netstandard2.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "netstandard2.0": { + "targetAlias": "netstandard2.0", + "projectReferences": {} + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "netstandard2.0": { + "targetAlias": "netstandard2.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[3.11.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.CSharp": { + "suppressParent": "All", + "target": "Package", + "version": "[5.0.0, )", + "versionCentrallyManaged": true + }, + "NETStandard.Library": { + "suppressParent": "All", + "target": "Package", + "version": "[2.0.3, )", + "autoReferenced": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/RuntimeIdentifierGraph.json" + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "version": "10.11.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "projectName": "Jellyfin.Database.Implementations", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Design": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Relational": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Tools": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Polly": { + "target": "Package", + "version": "[8.6.5, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj", + "projectName": "Jellyfin.Database.Providers.Sqlite", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Design": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Relational": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Sqlite": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Tools": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj", + "projectName": "Jellyfin.Drawing", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "AsyncKeyedLock": { + "target": "Package", + "version": "[8.0.2, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "projectName": "Jellyfin.Extensions", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "noWarn": [ + "NU5104" + ], + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "Diacritics": { + "target": "Package", + "version": "[4.1.4, )", + "versionCentrallyManaged": true + }, + "ICU4N.Transliterator": { + "target": "Package", + "version": "[60.1.0-alpha.356, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj", + "projectName": "Jellyfin.MediaEncoding.Hls", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj": { + "version": "10.11.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj", + "projectName": "Jellyfin.MediaEncoding.Keyframes", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "NEbml": { + "target": "Package", + "version": "[1.1.0.5, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj", + "projectName": "Jellyfin.Networking", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + } + } +} \ No newline at end of file diff --git a/Emby.Server.Implementations/obj/Emby.Server.Implementations.csproj.nuget.g.props b/Emby.Server.Implementations/obj/Emby.Server.Implementations.csproj.nuget.g.props new file mode 100644 index 00000000..e4c0dd11 --- /dev/null +++ b/Emby.Server.Implementations/obj/Emby.Server.Implementations.csproj.nuget.g.props @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess> + <RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool> + <ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile> + <NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageRoot> + <NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageFolders> + <NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle> + <NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion> + </PropertyGroup> + <ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <SourceRoot Include="/home/wjones/.nuget/packages/" /> + </ItemGroup> + <ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props')" /> + <Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props')" /> + </ImportGroup> + <PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <PkgMicrosoft_Extensions_ApiDescription_Server Condition=" '$(PkgMicrosoft_Extensions_ApiDescription_Server)' == '' ">/home/wjones/.nuget/packages/microsoft.extensions.apidescription.server/6.0.5</PkgMicrosoft_Extensions_ApiDescription_Server> + <PkgStyleCop_Analyzers_Unstable Condition=" '$(PkgStyleCop_Analyzers_Unstable)' == '' ">/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556</PkgStyleCop_Analyzers_Unstable> + <PkgSmartAnalyzers_MultithreadingAnalyzer Condition=" '$(PkgSmartAnalyzers_MultithreadingAnalyzer)' == '' ">/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31</PkgSmartAnalyzers_MultithreadingAnalyzer> + <PkgSerilogAnalyzer Condition=" '$(PkgSerilogAnalyzer)' == '' ">/home/wjones/.nuget/packages/seriloganalyzer/0.15.0</PkgSerilogAnalyzer> + <PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers Condition=" '$(PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers)' == '' ">/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0</PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/Emby.Server.Implementations/obj/Emby.Server.Implementations.csproj.nuget.g.targets b/Emby.Server.Implementations/obj/Emby.Server.Implementations.csproj.nuget.g.targets new file mode 100644 index 00000000..8b33b430 --- /dev/null +++ b/Emby.Server.Implementations/obj/Emby.Server.Implementations.csproj.nuget.g.targets @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <Import Project="$(NuGetPackageRoot)sqlitepclraw.lib.e_sqlite3/2.1.11/buildTransitive/net9.0/SQLitePCLRaw.lib.e_sqlite3.targets" Condition="Exists('$(NuGetPackageRoot)sqlitepclraw.lib.e_sqlite3/2.1.11/buildTransitive/net9.0/SQLitePCLRaw.lib.e_sqlite3.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.binder/10.0.3/buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.binder/10.0.3/buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets')" /> + </ImportGroup> +</Project> \ No newline at end of file diff --git a/Emby.Server.Implementations/obj/project.assets.json b/Emby.Server.Implementations/obj/project.assets.json new file mode 100644 index 00000000..9e3e65c8 --- /dev/null +++ b/Emby.Server.Implementations/obj/project.assets.json @@ -0,0 +1,4303 @@ +{ + "version": 3, + "targets": { + "net10.0": { + "AsyncKeyedLock/8.0.2": { + "type": "package", + "compile": { + "lib/net9.0/AsyncKeyedLock.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/AsyncKeyedLock.dll": { + "related": ".xml" + } + } + }, + "BDInfo/0.8.0": { + "type": "package", + "compile": { + "lib/net6.0/BDInfo.dll": {} + }, + "runtime": { + "lib/net6.0/BDInfo.dll": {} + } + }, + "BitFaster.Caching/2.5.4": { + "type": "package", + "compile": { + "lib/net6.0/BitFaster.Caching.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/BitFaster.Caching.dll": { + "related": ".xml" + } + } + }, + "Diacritics/4.1.4": { + "type": "package", + "compile": { + "lib/net9.0/Diacritics.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Diacritics.dll": { + "related": ".xml" + } + } + }, + "DiscUtils.Core/0.16.13": { + "type": "package", + "dependencies": { + "DiscUtils.Streams": "0.16.13" + }, + "compile": { + "lib/net5.0/DiscUtils.Core.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net5.0/DiscUtils.Core.dll": { + "related": ".xml" + } + } + }, + "DiscUtils.Iso9660/0.16.13": { + "type": "package", + "dependencies": { + "DiscUtils.Core": "0.16.13" + }, + "compile": { + "lib/net5.0/DiscUtils.Iso9660.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net5.0/DiscUtils.Iso9660.dll": { + "related": ".xml" + } + } + }, + "DiscUtils.Streams/0.16.13": { + "type": "package", + "compile": { + "lib/net5.0/DiscUtils.Streams.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net5.0/DiscUtils.Streams.dll": { + "related": ".xml" + } + } + }, + "DiscUtils.Udf/0.16.13": { + "type": "package", + "dependencies": { + "DiscUtils.Core": "0.16.13", + "DiscUtils.Iso9660": "0.16.13" + }, + "compile": { + "lib/net5.0/DiscUtils.Udf.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net5.0/DiscUtils.Udf.dll": { + "related": ".xml" + } + } + }, + "DotNet.Glob/3.1.3": { + "type": "package", + "compile": { + "lib/netcoreapp2.1/DotNet.Glob.dll": {} + }, + "runtime": { + "lib/netcoreapp2.1/DotNet.Glob.dll": {} + } + }, + "ICU4N/60.1.0-alpha.356": { + "type": "package", + "dependencies": { + "J2N": "2.0.0", + "Microsoft.Extensions.Caching.Memory": "2.0.0" + }, + "compile": { + "lib/netstandard2.0/ICU4N.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/ICU4N.dll": { + "related": ".xml" + } + } + }, + "ICU4N.Transliterator/60.1.0-alpha.356": { + "type": "package", + "dependencies": { + "ICU4N": "60.1.0-alpha.356" + }, + "compile": { + "lib/netstandard2.0/ICU4N.Transliterator.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/ICU4N.Transliterator.dll": { + "related": ".xml" + } + } + }, + "Ignore/0.2.1": { + "type": "package", + "compile": { + "lib/net8.0/Ignore.dll": {} + }, + "runtime": { + "lib/net8.0/Ignore.dll": {} + } + }, + "J2N/2.0.0": { + "type": "package", + "compile": { + "lib/net6.0/J2N.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/J2N.dll": { + "related": ".xml" + } + } + }, + "libse/4.0.12": { + "type": "package", + "dependencies": { + "System.Drawing.Common": "9.0.2", + "UTF.Unknown": "2.5.1", + "zlib.net-mutliplatform": "1.0.8" + }, + "compile": { + "lib/netstandard2.1/libse.dll": {} + }, + "runtime": { + "lib/netstandard2.1/libse.dll": {} + }, + "contentFiles": { + "contentFiles/any/any/_._": { + "buildAction": "None", + "codeLanguage": "any", + "copyToOutput": false + } + } + }, + "LrcParser/2025.623.0": { + "type": "package", + "compile": { + "lib/netstandard2.1/LrcParser.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.1/LrcParser.dll": { + "related": ".xml" + } + } + }, + "MetaBrainz.Common/4.1.1": { + "type": "package", + "compile": { + "lib/net10.0/MetaBrainz.Common.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/MetaBrainz.Common.dll": { + "related": ".xml" + } + } + }, + "MetaBrainz.Common.Json/7.2.0": { + "type": "package", + "dependencies": { + "MetaBrainz.Common": "4.1.1" + }, + "compile": { + "lib/net10.0/MetaBrainz.Common.Json.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/MetaBrainz.Common.Json.dll": { + "related": ".xml" + } + } + }, + "MetaBrainz.MusicBrainz/8.0.1": { + "type": "package", + "dependencies": { + "MetaBrainz.Common": "4.1.1", + "MetaBrainz.Common.Json": "7.2.0" + }, + "compile": { + "lib/net10.0/MetaBrainz.MusicBrainz.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/MetaBrainz.MusicBrainz.dll": { + "related": ".xml" + } + } + }, + "Microsoft.AspNetCore.Authorization/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Metadata": "10.0.3", + "Microsoft.Extensions.Diagnostics": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.AspNetCore.Authorization.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.AspNetCore.Authorization.dll": { + "related": ".xml" + } + } + }, + "Microsoft.AspNetCore.Metadata/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.AspNetCore.Metadata.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.AspNetCore.Metadata.dll": { + "related": ".xml" + } + } + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers/4.14.0": { + "type": "package", + "build": { + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props": {}, + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets": {} + } + }, + "Microsoft.Data.Sqlite/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Data.Sqlite.Core": "10.0.3", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.11", + "SQLitePCLRaw.core": "2.1.11" + }, + "compile": { + "lib/netstandard2.0/_._": {} + }, + "runtime": { + "lib/netstandard2.0/_._": {} + } + }, + "Microsoft.Data.Sqlite.Core/10.0.3": { + "type": "package", + "dependencies": { + "SQLitePCLRaw.core": "2.1.11" + }, + "compile": { + "lib/net8.0/Microsoft.Data.Sqlite.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Microsoft.Data.Sqlite.dll": { + "related": ".xml" + } + } + }, + "Microsoft.EntityFrameworkCore/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "10.0.3", + "Microsoft.EntityFrameworkCore.Analyzers": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props": {} + } + }, + "Microsoft.EntityFrameworkCore.Abstractions/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + } + }, + "Microsoft.EntityFrameworkCore.Analyzers/10.0.3": { + "type": "package" + }, + "Microsoft.EntityFrameworkCore.Relational/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + } + }, + "Microsoft.EntityFrameworkCore.Sqlite/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore.Sqlite.Core": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.DependencyModel": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.11", + "SQLitePCLRaw.core": "2.1.11" + }, + "compile": { + "lib/net10.0/_._": {} + }, + "runtime": { + "lib/net10.0/_._": {} + } + }, + "Microsoft.EntityFrameworkCore.Sqlite.Core/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Data.Sqlite.Core": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.DependencyModel": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "SQLitePCLRaw.core": "2.1.11" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Sqlite.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Sqlite.dll": { + "related": ".xml" + } + } + }, + "Microsoft.Extensions.ApiDescription.Server/6.0.5": { + "type": "package", + "build": { + "build/_._": {} + }, + "buildMultiTargeting": { + "buildMultiTargeting/_._": {} + } + }, + "Microsoft.Extensions.Caching.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Caching.Memory/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Caching.Memory.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Caching.Memory.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Configuration/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Configuration.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Configuration.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Configuration.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Configuration.Binder/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Configuration.Binder.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Configuration.Binder.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets": {} + } + }, + "Microsoft.Extensions.DependencyInjection/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.DependencyModel/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.Extensions.DependencyModel.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.DependencyModel.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Diagnostics/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.3", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.3", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Diagnostics.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Diagnostics.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Diagnostics.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Diagnostics.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.FileProviders.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.FileProviders.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.FileProviders.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Hosting.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.3", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Hosting.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Hosting.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Http/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Diagnostics": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Http.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Http.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Logging/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Logging.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Logging.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Logging.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets": {} + } + }, + "Microsoft.Extensions.Options/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Options.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Options.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/Microsoft.Extensions.Options.targets": {} + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Primitives/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.Extensions.Primitives.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Primitives.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.OpenApi/1.6.22": { + "type": "package", + "compile": { + "lib/netstandard2.0/Microsoft.OpenApi.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/netstandard2.0/Microsoft.OpenApi.dll": { + "related": ".pdb;.xml" + } + } + }, + "Microsoft.Win32.SystemEvents/9.0.2": { + "type": "package", + "compile": { + "lib/net9.0/Microsoft.Win32.SystemEvents.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Win32.SystemEvents.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + }, + "runtimeTargets": { + "runtimes/win/lib/net9.0/Microsoft.Win32.SystemEvents.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "NEbml/1.1.0.5": { + "type": "package", + "compile": { + "lib/netstandard2.0/NEbml.Core.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/NEbml.Core.dll": { + "related": ".xml" + } + } + }, + "Newtonsoft.Json/13.0.4": { + "type": "package", + "compile": { + "lib/net6.0/Newtonsoft.Json.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/Newtonsoft.Json.dll": { + "related": ".xml" + } + } + }, + "PlaylistsNET/1.4.1": { + "type": "package", + "compile": { + "lib/net8.0/PlaylistsNET.dll": {} + }, + "runtime": { + "lib/net8.0/PlaylistsNET.dll": {} + } + }, + "Polly/8.6.5": { + "type": "package", + "dependencies": { + "Polly.Core": "8.6.5" + }, + "compile": { + "lib/net6.0/Polly.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net6.0/Polly.dll": { + "related": ".pdb;.xml" + } + } + }, + "Polly.Core/8.6.5": { + "type": "package", + "compile": { + "lib/net8.0/Polly.Core.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net8.0/Polly.Core.dll": { + "related": ".pdb;.xml" + } + } + }, + "prometheus-net/3.1.2": { + "type": "package", + "compile": { + "lib/netstandard2.0/Prometheus.NetStandard.dll": { + "related": ".deps.json;.pdb;.xml" + } + }, + "runtime": { + "lib/netstandard2.0/Prometheus.NetStandard.dll": { + "related": ".deps.json;.pdb;.xml" + } + } + }, + "prometheus-net.DotNetRuntime/4.4.1": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "3.0.0", + "prometheus-net": "3.1.2" + }, + "compile": { + "lib/net6.0/prometheus-net.DotNetRuntime.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net6.0/prometheus-net.DotNetRuntime.dll": { + "related": ".pdb;.xml" + } + } + }, + "SerilogAnalyzer/0.15.0": { + "type": "package" + }, + "SmartAnalyzers.MultithreadingAnalyzer/1.1.31": { + "type": "package" + }, + "SQLitePCLRaw.bundle_e_sqlite3/2.1.11": { + "type": "package", + "dependencies": { + "SQLitePCLRaw.lib.e_sqlite3": "2.1.11", + "SQLitePCLRaw.provider.e_sqlite3": "2.1.11" + }, + "compile": { + "lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll": {} + }, + "runtime": { + "lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll": {} + } + }, + "SQLitePCLRaw.core/2.1.11": { + "type": "package", + "compile": { + "lib/netstandard2.0/SQLitePCLRaw.core.dll": {} + }, + "runtime": { + "lib/netstandard2.0/SQLitePCLRaw.core.dll": {} + } + }, + "SQLitePCLRaw.lib.e_sqlite3/2.1.11": { + "type": "package", + "compile": { + "lib/netstandard2.0/_._": {} + }, + "runtime": { + "lib/netstandard2.0/_._": {} + }, + "build": { + "buildTransitive/net9.0/SQLitePCLRaw.lib.e_sqlite3.targets": {} + }, + "runtimeTargets": { + "runtimes/browser-wasm/nativeassets/net9.0/e_sqlite3.a": { + "assetType": "native", + "rid": "browser-wasm" + }, + "runtimes/linux-arm/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-arm" + }, + "runtimes/linux-arm64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-arm64" + }, + "runtimes/linux-armel/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-armel" + }, + "runtimes/linux-mips64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-mips64" + }, + "runtimes/linux-musl-arm/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-arm" + }, + "runtimes/linux-musl-arm64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-arm64" + }, + "runtimes/linux-musl-riscv64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-riscv64" + }, + "runtimes/linux-musl-s390x/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-s390x" + }, + "runtimes/linux-musl-x64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-x64" + }, + "runtimes/linux-ppc64le/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-ppc64le" + }, + "runtimes/linux-riscv64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-riscv64" + }, + "runtimes/linux-s390x/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-s390x" + }, + "runtimes/linux-x64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-x64" + }, + "runtimes/linux-x86/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-x86" + }, + "runtimes/maccatalyst-arm64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "maccatalyst-arm64" + }, + "runtimes/maccatalyst-x64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "maccatalyst-x64" + }, + "runtimes/osx-arm64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "osx-arm64" + }, + "runtimes/osx-x64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "osx-x64" + }, + "runtimes/win-arm/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-arm" + }, + "runtimes/win-arm64/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-arm64" + }, + "runtimes/win-x64/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-x64" + }, + "runtimes/win-x86/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-x86" + } + } + }, + "SQLitePCLRaw.provider.e_sqlite3/2.1.11": { + "type": "package", + "dependencies": { + "SQLitePCLRaw.core": "2.1.11" + }, + "compile": { + "lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll": {} + }, + "runtime": { + "lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll": {} + } + }, + "StyleCop.Analyzers/1.2.0-beta.556": { + "type": "package", + "dependencies": { + "StyleCop.Analyzers.Unstable": "1.2.0.556" + } + }, + "StyleCop.Analyzers.Unstable/1.2.0.556": { + "type": "package" + }, + "Swashbuckle.AspNetCore/7.3.2": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.ApiDescription.Server": "6.0.5", + "Swashbuckle.AspNetCore.Swagger": "7.3.2", + "Swashbuckle.AspNetCore.SwaggerGen": "7.3.2", + "Swashbuckle.AspNetCore.SwaggerUI": "7.3.2" + }, + "build": { + "build/_._": {} + }, + "buildMultiTargeting": { + "buildMultiTargeting/_._": {} + } + }, + "Swashbuckle.AspNetCore.ReDoc/6.9.0": { + "type": "package", + "compile": { + "lib/net8.0/Swashbuckle.AspNetCore.ReDoc.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net8.0/Swashbuckle.AspNetCore.ReDoc.dll": { + "related": ".pdb;.xml" + } + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Swashbuckle.AspNetCore.Swagger/7.3.2": { + "type": "package", + "dependencies": { + "Microsoft.OpenApi": "1.6.22" + }, + "compile": { + "lib/net9.0/Swashbuckle.AspNetCore.Swagger.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net9.0/Swashbuckle.AspNetCore.Swagger.dll": { + "related": ".pdb;.xml" + } + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Swashbuckle.AspNetCore.SwaggerGen/7.3.2": { + "type": "package", + "dependencies": { + "Swashbuckle.AspNetCore.Swagger": "7.3.2" + }, + "compile": { + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll": { + "related": ".pdb;.xml" + } + } + }, + "Swashbuckle.AspNetCore.SwaggerUI/7.3.2": { + "type": "package", + "compile": { + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll": { + "related": ".pdb;.xml" + } + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "System.Drawing.Common/9.0.2": { + "type": "package", + "dependencies": { + "Microsoft.Win32.SystemEvents": "9.0.2" + }, + "compile": { + "lib/net9.0/System.Drawing.Common.dll": { + "related": ".pdb;.xml" + }, + "lib/net9.0/System.Private.Windows.Core.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/System.Drawing.Common.dll": { + "related": ".pdb;.xml" + }, + "lib/net9.0/System.Private.Windows.Core.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "TagLibSharp/2.3.0": { + "type": "package", + "compile": { + "lib/netstandard2.0/TagLibSharp.dll": { + "related": ".pdb" + } + }, + "runtime": { + "lib/netstandard2.0/TagLibSharp.dll": { + "related": ".pdb" + } + } + }, + "TMDbLib/2.3.0": { + "type": "package", + "dependencies": { + "Newtonsoft.Json": "13.0.3" + }, + "compile": { + "lib/netstandard2.1/TMDbLib.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.1/TMDbLib.dll": { + "related": ".xml" + } + } + }, + "Ude.NetStandard/1.2.0": { + "type": "package", + "compile": { + "lib/netstandard2.0/Ude.NetStandard.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Ude.NetStandard.dll": {} + } + }, + "UTF.Unknown/2.6.0": { + "type": "package", + "compile": { + "lib/net8.0/UtfUnknown.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/UtfUnknown.dll": { + "related": ".xml" + } + } + }, + "z440.atl.core/7.11.0": { + "type": "package", + "dependencies": { + "Ude.NetStandard": "1.2.0" + }, + "compile": { + "lib/net6.0/ATL.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/ATL.dll": { + "related": ".xml" + } + } + }, + "zlib.net-mutliplatform/1.0.8": { + "type": "package", + "compile": { + "lib/netstandard2.1/zlib.net.dll": {} + }, + "runtime": { + "lib/netstandard2.1/zlib.net.dll": {} + } + }, + "Emby.Photos/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.Model": "10.12.0", + "TagLibSharp": "2.3.0" + }, + "compile": { + "bin/placeholder/Emby.Photos.dll": {} + }, + "runtime": { + "bin/placeholder/Emby.Photos.dll": {} + } + }, + "Jellyfin.Api/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.MediaEncoding.Hls": "1.0.0", + "Jellyfin.Networking": "1.0.0", + "MediaBrowser.MediaEncoding": "1.0.0", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Api.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Api.dll": {} + } + }, + "Jellyfin.CodeAnalysis/1.0.0": { + "type": "project", + "framework": ".NETStandard,Version=v2.0", + "compile": { + "bin/placeholder/Jellyfin.CodeAnalysis.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.CodeAnalysis.dll": {} + } + }, + "Jellyfin.Common/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Common.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Common.dll": {} + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Jellyfin.Controller/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "BitFaster.Caching": "2.5.4", + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.MediaEncoding.Keyframes": "10.11.0", + "Jellyfin.Model": "10.12.0", + "Jellyfin.Naming": "10.12.0", + "Microsoft.Extensions.Configuration.Binder": "10.0.3" + }, + "compile": { + "bin/placeholder/Jellyfin.Controller.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Controller.dll": {} + } + }, + "Jellyfin.Data/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Database.Implementations": "10.11.0", + "Microsoft.Extensions.Logging": "10.0.3" + }, + "compile": { + "bin/placeholder/Jellyfin.Data.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Data.dll": {} + } + }, + "Jellyfin.Database.Implementations/10.11.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Polly": "8.6.5" + }, + "compile": { + "bin/placeholder/Jellyfin.Database.Implementations.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Database.Implementations.dll": {} + } + }, + "Jellyfin.Database.Providers.Sqlite/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Database.Implementations": "10.11.0", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3" + }, + "compile": { + "bin/placeholder/Jellyfin.Database.Providers.Sqlite.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Database.Providers.Sqlite.dll": {} + } + }, + "Jellyfin.Drawing/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "AsyncKeyedLock": "8.0.2", + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Drawing.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Drawing.dll": {} + } + }, + "Jellyfin.Extensions/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Diacritics": "4.1.4", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "Jellyfin.CodeAnalysis": "1.0.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Extensions.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Extensions.dll": {} + } + }, + "Jellyfin.MediaEncoding.Hls/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.MediaEncoding.Keyframes": "10.11.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.MediaEncoding.Hls.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.MediaEncoding.Hls.dll": {} + } + }, + "Jellyfin.MediaEncoding.Keyframes/10.11.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "NEbml": "1.1.0.5" + }, + "compile": { + "bin/placeholder/Jellyfin.MediaEncoding.Keyframes.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.MediaEncoding.Keyframes.dll": {} + } + }, + "Jellyfin.Model/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Data": "10.12.0", + "Jellyfin.Extensions": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Model.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Model.dll": {} + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Jellyfin.Naming/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Naming.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Naming.dll": {} + } + }, + "Jellyfin.Networking/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Controller": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Networking.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Networking.dll": {} + } + }, + "Jellyfin.Server.Implementations/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "AsyncKeyedLock": "8.0.2", + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.Data": "10.12.0", + "Jellyfin.Database.Implementations": "10.11.0", + "Jellyfin.Database.Providers.Sqlite": "1.0.0", + "Jellyfin.Model": "10.12.0", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3" + }, + "compile": { + "bin/placeholder/Jellyfin.Server.Implementations.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Server.Implementations.dll": {} + } + }, + "MediaBrowser.LocalMetadata/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/MediaBrowser.LocalMetadata.dll": {} + }, + "runtime": { + "bin/placeholder/MediaBrowser.LocalMetadata.dll": {} + } + }, + "MediaBrowser.MediaEncoding/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "AsyncKeyedLock": "8.0.2", + "BDInfo": "0.8.0", + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.Model": "10.12.0", + "Microsoft.Extensions.Http": "10.0.3", + "UTF.Unknown": "2.6.0", + "libse": "4.0.12" + }, + "compile": { + "bin/placeholder/MediaBrowser.MediaEncoding.dll": {} + }, + "runtime": { + "bin/placeholder/MediaBrowser.MediaEncoding.dll": {} + } + }, + "MediaBrowser.Providers/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "AsyncKeyedLock": "8.0.2", + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.Model": "10.12.0", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "TMDbLib": "2.3.0", + "z440.atl.core": "7.11.0" + }, + "compile": { + "bin/placeholder/MediaBrowser.Providers.dll": {} + }, + "runtime": { + "bin/placeholder/MediaBrowser.Providers.dll": {} + } + }, + "MediaBrowser.XbmcMetadata/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/MediaBrowser.XbmcMetadata.dll": {} + }, + "runtime": { + "bin/placeholder/MediaBrowser.XbmcMetadata.dll": {} + } + } + } + }, + "libraries": { + "AsyncKeyedLock/8.0.2": { + "sha512": "QGys5cnIerNryv7V14PDkvGnlLz69kJtTfdnr+Lndcu+lRre397RNyU4FIeAJWgI9u73lTzXL52Qca9B/ncLXw==", + "type": "package", + "path": "asynckeyedlock/8.0.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "asynckeyedlock.8.0.2.nupkg.sha512", + "asynckeyedlock.nuspec", + "lib/net5.0/AsyncKeyedLock.dll", + "lib/net5.0/AsyncKeyedLock.xml", + "lib/net6.0/AsyncKeyedLock.dll", + "lib/net6.0/AsyncKeyedLock.xml", + "lib/net7.0/AsyncKeyedLock.dll", + "lib/net7.0/AsyncKeyedLock.xml", + "lib/net8.0/AsyncKeyedLock.dll", + "lib/net8.0/AsyncKeyedLock.xml", + "lib/net9.0/AsyncKeyedLock.dll", + "lib/net9.0/AsyncKeyedLock.xml", + "lib/netstandard2.0/AsyncKeyedLock.dll", + "lib/netstandard2.0/AsyncKeyedLock.xml", + "lib/netstandard2.1/AsyncKeyedLock.dll", + "lib/netstandard2.1/AsyncKeyedLock.xml", + "logo.png" + ] + }, + "BDInfo/0.8.0": { + "sha512": "9pz06aM0ceknGQ410IqqO0176ZPp2JPX00ekDDICaiU5sVh2qThOIAEfID08QWPS0xoM/JYtVsO42uDVf+08MQ==", + "type": "package", + "path": "bdinfo/0.8.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "bdinfo.0.8.0.nupkg.sha512", + "bdinfo.nuspec", + "lib/net6.0/BDInfo.dll", + "lib/netstandard2.1/BDInfo.dll" + ] + }, + "BitFaster.Caching/2.5.4": { + "sha512": "1QroTY1PVCZOSG9FnkkCrmCKk/+bZCgI/YXq376HnYwUDJ4Ho0EaV4YaA/5v5WYLnwIwIO7RZkdWbg9pxIpueQ==", + "type": "package", + "path": "bitfaster.caching/2.5.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "ReadMe.md", + "bitfaster.caching.2.5.4.nupkg.sha512", + "bitfaster.caching.nuspec", + "lib/net6.0/BitFaster.Caching.dll", + "lib/net6.0/BitFaster.Caching.xml", + "lib/netcoreapp3.1/BitFaster.Caching.dll", + "lib/netcoreapp3.1/BitFaster.Caching.xml", + "lib/netstandard2.0/BitFaster.Caching.dll", + "lib/netstandard2.0/BitFaster.Caching.xml" + ] + }, + "Diacritics/4.1.4": { + "sha512": "9JK2bI+oWnzOK4Lu+5FXt7czofD28AMnZ95np5fkSClqioPnga1JVpjaUxVJaEpc/5HIlfikc6YI33lm2qu4mw==", + "type": "package", + "path": "diacritics/4.1.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "README.md", + "diacritics.4.1.4.nupkg.sha512", + "diacritics.nuspec", + "lib/net48/Diacritics.dll", + "lib/net48/Diacritics.xml", + "lib/net7.0/Diacritics.dll", + "lib/net7.0/Diacritics.xml", + "lib/net8.0/Diacritics.dll", + "lib/net8.0/Diacritics.xml", + "lib/net9.0/Diacritics.dll", + "lib/net9.0/Diacritics.xml", + "lib/netstandard1.2/Diacritics.dll", + "lib/netstandard1.2/Diacritics.xml", + "lib/netstandard2.0/Diacritics.dll", + "lib/netstandard2.0/Diacritics.xml", + "lib/netstandard2.1/Diacritics.dll", + "lib/netstandard2.1/Diacritics.xml", + "logo.png" + ] + }, + "DiscUtils.Core/0.16.13": { + "sha512": "TU/Ns5RMa3060K7AsSDqG94/H7eaWaz1XoYuCUur40Mg+t0/ubtKqxCffkoiB1e/cFMGvHL0evFoY6uDL9Ocmw==", + "type": "package", + "path": "discutils.core/0.16.13", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "discutils.core.0.16.13.nupkg.sha512", + "discutils.core.nuspec", + "lib/net40/DiscUtils.Core.dll", + "lib/net40/DiscUtils.Core.xml", + "lib/net45/DiscUtils.Core.dll", + "lib/net45/DiscUtils.Core.xml", + "lib/net5.0/DiscUtils.Core.dll", + "lib/net5.0/DiscUtils.Core.xml", + "lib/netstandard2.0/DiscUtils.Core.dll", + "lib/netstandard2.0/DiscUtils.Core.xml" + ] + }, + "DiscUtils.Iso9660/0.16.13": { + "sha512": "hT0DbN+sIqg5Bu3/sCQUuKLwIrceF9BZvn7AeRe+f2iY0p4mgN9xEmXpFWQshnBBxN9r75364agBHDeb5owiUA==", + "type": "package", + "path": "discutils.iso9660/0.16.13", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "discutils.iso9660.0.16.13.nupkg.sha512", + "discutils.iso9660.nuspec", + "lib/net40/DiscUtils.Iso9660.dll", + "lib/net40/DiscUtils.Iso9660.xml", + "lib/net45/DiscUtils.Iso9660.dll", + "lib/net45/DiscUtils.Iso9660.xml", + "lib/net5.0/DiscUtils.Iso9660.dll", + "lib/net5.0/DiscUtils.Iso9660.xml", + "lib/netstandard2.0/DiscUtils.Iso9660.dll", + "lib/netstandard2.0/DiscUtils.Iso9660.xml" + ] + }, + "DiscUtils.Streams/0.16.13": { + "sha512": "BSrywAX2mmvt6j00u6VQYQLtxc6jDniK2aP4p0q20bbTGXImtW4N26eUZYKfkW9aj6XLaqISFgtWfssKURixeA==", + "type": "package", + "path": "discutils.streams/0.16.13", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "discutils.streams.0.16.13.nupkg.sha512", + "discutils.streams.nuspec", + "lib/net40/DiscUtils.Streams.dll", + "lib/net40/DiscUtils.Streams.xml", + "lib/net45/DiscUtils.Streams.dll", + "lib/net45/DiscUtils.Streams.xml", + "lib/net5.0/DiscUtils.Streams.dll", + "lib/net5.0/DiscUtils.Streams.xml", + "lib/netstandard2.0/DiscUtils.Streams.dll", + "lib/netstandard2.0/DiscUtils.Streams.xml" + ] + }, + "DiscUtils.Udf/0.16.13": { + "sha512": "wl8amQNP3N5VFB4wlLh76OXD2vGpFKuYb/RqqcwTPdE406mvQsLuxxPJB5E4orkjNDrpglLvi94WVr8FfWY2Uw==", + "type": "package", + "path": "discutils.udf/0.16.13", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "discutils.udf.0.16.13.nupkg.sha512", + "discutils.udf.nuspec", + "lib/net40/DiscUtils.Udf.dll", + "lib/net40/DiscUtils.Udf.xml", + "lib/net45/DiscUtils.Udf.dll", + "lib/net45/DiscUtils.Udf.xml", + "lib/net5.0/DiscUtils.Udf.dll", + "lib/net5.0/DiscUtils.Udf.xml", + "lib/netstandard2.0/DiscUtils.Udf.dll", + "lib/netstandard2.0/DiscUtils.Udf.xml" + ] + }, + "DotNet.Glob/3.1.3": { + "sha512": "hOfHw7MLJw/tbXaFwR1oiDb+dIXDp8URTxp5Pco42OOhiw77wrUNx6v6syNygHZbWwYdXQocL2Mo1l5FnfDVjg==", + "type": "package", + "path": "dotnet.glob/3.1.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "dotnet.glob.3.1.3.nupkg.sha512", + "dotnet.glob.nuspec", + "lib/net40/DotNet.Glob.dll", + "lib/net45/DotNet.Glob.dll", + "lib/net46/DotNet.Glob.dll", + "lib/netcoreapp2.1/DotNet.Glob.dll", + "lib/netstandard1.1/DotNet.Glob.dll", + "lib/netstandard2.1/DotNet.Glob.dll" + ] + }, + "ICU4N/60.1.0-alpha.356": { + "sha512": "YMZtDnjcqWzziOKiE7w6Ma7Rl5vuFDxzOsUlHh1QyfghbNEIZQOLRs9MMfwCWAjX6n9UitrF6vLXy55Z5q+4Fg==", + "type": "package", + "path": "icu4n/60.1.0-alpha.356", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "icu4n.60.1.0-alpha.356.nupkg.sha512", + "icu4n.nuspec", + "lib/net40/ICU4N.dll", + "lib/net40/ICU4N.xml", + "lib/net451/ICU4N.dll", + "lib/net451/ICU4N.xml", + "lib/netstandard2.0/ICU4N.dll", + "lib/netstandard2.0/ICU4N.xml", + "unicode-icon-128x128.jpg" + ] + }, + "ICU4N.Transliterator/60.1.0-alpha.356": { + "sha512": "lFOSO6bbEtB6HkWMNDJAq+rFwVyi9g6xVc5O/2xHa6iZnV7wLVDqCbaQ4W4vIeBSQZAafqhxciaEkmAvSdzlCg==", + "type": "package", + "path": "icu4n.transliterator/60.1.0-alpha.356", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "icu4n.transliterator.60.1.0-alpha.356.nupkg.sha512", + "icu4n.transliterator.nuspec", + "lib/net40/ICU4N.Transliterator.dll", + "lib/net40/ICU4N.Transliterator.xml", + "lib/net451/ICU4N.Transliterator.dll", + "lib/net451/ICU4N.Transliterator.xml", + "lib/netstandard2.0/ICU4N.Transliterator.dll", + "lib/netstandard2.0/ICU4N.Transliterator.xml", + "unicode-icon-128x128.jpg" + ] + }, + "Ignore/0.2.1": { + "sha512": "Qw3s0pTwK3o6Iv6kTMjmxzOt91pczU533OmtAvFRsJ7PdCVMhGCRyUkMsCOI7ejxOtHJdRsj141HeZWeedlqkQ==", + "type": "package", + "path": "ignore/0.2.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ignore.0.2.1.nupkg.sha512", + "ignore.nuspec", + "lib/net8.0/Ignore.dll", + "lib/netstandard2.0/Ignore.dll" + ] + }, + "J2N/2.0.0": { + "sha512": "M5bwDajAARZiyqupU+rHQJnsVLxNBOHJ8vKYHd8LcLIb1FgLfzzcJvc31Qo5Xz/GEHFjDF9ScjKL/ks/zRTXuA==", + "type": "package", + "path": "j2n/2.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "j2n-icon-100x100.png", + "j2n.2.0.0.nupkg.sha512", + "j2n.nuspec", + "lib/net40/J2N.dll", + "lib/net40/J2N.xml", + "lib/net45/J2N.dll", + "lib/net45/J2N.xml", + "lib/net5.0/J2N.dll", + "lib/net5.0/J2N.xml", + "lib/net6.0/J2N.dll", + "lib/net6.0/J2N.xml", + "lib/netcoreapp3.0/J2N.dll", + "lib/netcoreapp3.0/J2N.xml", + "lib/netstandard2.0/J2N.dll", + "lib/netstandard2.0/J2N.xml", + "lib/netstandard2.1/J2N.dll", + "lib/netstandard2.1/J2N.xml", + "readme.md" + ] + }, + "libse/4.0.12": { + "sha512": "vI4V5rwg5by28SFZXHPciIinwZ4khaE5Y5SS/rTzKxlqxrdmx2RLUrCReejjRfgtVZfvWFKnODTmr2kQ6Qnl2Q==", + "type": "package", + "path": "libse/4.0.12", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.txt", + "Readme.md", + "content/Icon.png", + "contentFiles/any/net48/Icon.png", + "contentFiles/any/netstandard2.1/Icon.png", + "lib/net48/libse.dll", + "lib/netstandard2.1/libse.dll", + "libse.4.0.12.nupkg.sha512", + "libse.nuspec" + ] + }, + "LrcParser/2025.623.0": { + "sha512": "gJRCaoVeYfN36HAGzav75T/Q6NsfYEkK4p6PiVTF6R/NU/JL9U6ZXOw1dodYe81GzaQpef/JNX0/drtJ6ZofMA==", + "type": "package", + "path": "lrcparser/2025.623.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/netstandard2.1/LrcParser.dll", + "lib/netstandard2.1/LrcParser.xml", + "lrcparser.2025.623.0.nupkg.sha512", + "lrcparser.nuspec" + ] + }, + "MetaBrainz.Common/4.1.1": { + "sha512": "ovKr4BktLkKdGcMkKJihcGNPqvsPW4DXRmi051SbJICauVkKgnZFI51uWaHoQoO8Z6YBTqLzqam/GfRlMj0xcw==", + "type": "package", + "path": "metabrainz.common/4.1.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.md", + "README.md", + "lib/net10.0/MetaBrainz.Common.dll", + "lib/net10.0/MetaBrainz.Common.xml", + "lib/net8.0/MetaBrainz.Common.dll", + "lib/net8.0/MetaBrainz.Common.xml", + "metabrainz.common.4.1.1.nupkg.sha512", + "metabrainz.common.nuspec", + "package-icon.png" + ] + }, + "MetaBrainz.Common.Json/7.2.0": { + "sha512": "e3lPRMTujYYzBzZI7GV3s4OwXdBViGGrdpvWf5J/PXFaasjEdkQvKpmwG26PplZ3atPRK8x3FRVgP4lOARAoLQ==", + "type": "package", + "path": "metabrainz.common.json/7.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.md", + "README.md", + "lib/net10.0/MetaBrainz.Common.Json.dll", + "lib/net10.0/MetaBrainz.Common.Json.xml", + "lib/net8.0/MetaBrainz.Common.Json.dll", + "lib/net8.0/MetaBrainz.Common.Json.xml", + "metabrainz.common.json.7.2.0.nupkg.sha512", + "metabrainz.common.json.nuspec", + "package-icon.png" + ] + }, + "MetaBrainz.MusicBrainz/8.0.1": { + "sha512": "CXyfOQbYyICPTOckS169V4+zmk71sd2z6FncoQxE3uR1fvwyD5Q7+Iv5/mAmqnb/u6IyepMt+2+O8lzDLmXZbg==", + "type": "package", + "path": "metabrainz.musicbrainz/8.0.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.md", + "README.md", + "lib/net10.0/MetaBrainz.MusicBrainz.dll", + "lib/net10.0/MetaBrainz.MusicBrainz.xml", + "lib/net8.0/MetaBrainz.MusicBrainz.dll", + "lib/net8.0/MetaBrainz.MusicBrainz.xml", + "metabrainz.musicbrainz.8.0.1.nupkg.sha512", + "metabrainz.musicbrainz.nuspec", + "package-icon.png" + ] + }, + "Microsoft.AspNetCore.Authorization/10.0.3": { + "sha512": "pqm2ivtD2bj5f+4KnrGmJsD/iDZkMnJnK/uW/p1bpqKCR316TyWqyhhS5znLGw7QpX2fAWhXU+uQo1Cb89bedA==", + "type": "package", + "path": "microsoft.aspnetcore.authorization/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "THIRD-PARTY-NOTICES.TXT", + "lib/net10.0/Microsoft.AspNetCore.Authorization.dll", + "lib/net10.0/Microsoft.AspNetCore.Authorization.xml", + "lib/net462/Microsoft.AspNetCore.Authorization.dll", + "lib/net462/Microsoft.AspNetCore.Authorization.xml", + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.xml", + "microsoft.aspnetcore.authorization.10.0.3.nupkg.sha512", + "microsoft.aspnetcore.authorization.nuspec" + ] + }, + "Microsoft.AspNetCore.Metadata/10.0.3": { + "sha512": "PtLHFABwDpGhpTMxni8z4W0J2b+y2EVFkpZ8K6A092pbdBdlD3yAgxAZhwLxXl2RKBTuVj5TUGc2voDQ/ghpTA==", + "type": "package", + "path": "microsoft.aspnetcore.metadata/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "THIRD-PARTY-NOTICES.TXT", + "lib/net10.0/Microsoft.AspNetCore.Metadata.dll", + "lib/net10.0/Microsoft.AspNetCore.Metadata.xml", + "lib/net462/Microsoft.AspNetCore.Metadata.dll", + "lib/net462/Microsoft.AspNetCore.Metadata.xml", + "lib/netstandard2.0/Microsoft.AspNetCore.Metadata.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Metadata.xml", + "microsoft.aspnetcore.metadata.10.0.3.nupkg.sha512", + "microsoft.aspnetcore.metadata.nuspec" + ] + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers/4.14.0": { + "sha512": "gSWJlDWwmDhtbrEJGiHqvEjz9KthIiFD0qYB8zZ6a7z+xpMSPEtM9yTYELSa58iFWYlzSRqP9FXO6KoT3+ZMtg==", + "type": "package", + "path": "microsoft.codeanalysis.bannedapianalyzers/4.14.0", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.txt", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.dll", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.CSharp.BannedApiAnalyzers.dll", + "analyzers/dotnet/cs/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/de/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/es/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/fr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/it/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ja/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ko/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/pl/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/pt-BR/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ru/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/tr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hans/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hant/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.BannedApiAnalyzers.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.VisualBasic.BannedApiAnalyzers.dll", + "analyzers/dotnet/vb/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/de/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/es/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/fr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/it/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ja/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ko/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/pl/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/pt-BR/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ru/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/tr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hans/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hant/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props", + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets", + "documentation/Microsoft.CodeAnalysis.BannedApiAnalyzers.md", + "documentation/Microsoft.CodeAnalysis.BannedApiAnalyzers.sarif", + "editorconfig/AllRulesDefault/.editorconfig", + "editorconfig/AllRulesDisabled/.editorconfig", + "editorconfig/AllRulesEnabled/.editorconfig", + "editorconfig/ApiDesignRulesDefault/.editorconfig", + "editorconfig/ApiDesignRulesEnabled/.editorconfig", + "editorconfig/DataflowRulesDefault/.editorconfig", + "editorconfig/DataflowRulesEnabled/.editorconfig", + "editorconfig/PortedFromFxCopRulesDefault/.editorconfig", + "editorconfig/PortedFromFxCopRulesEnabled/.editorconfig", + "microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512", + "microsoft.codeanalysis.bannedapianalyzers.nuspec", + "rulesets/AllRulesDefault.ruleset", + "rulesets/AllRulesDisabled.ruleset", + "rulesets/AllRulesEnabled.ruleset", + "rulesets/ApiDesignRulesDefault.ruleset", + "rulesets/ApiDesignRulesEnabled.ruleset", + "rulesets/DataflowRulesDefault.ruleset", + "rulesets/DataflowRulesEnabled.ruleset", + "rulesets/PortedFromFxCopRulesDefault.ruleset", + "rulesets/PortedFromFxCopRulesEnabled.ruleset", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Microsoft.Data.Sqlite/10.0.3": { + "sha512": "R/7g5hddFTzey+XOH7Hb3x71DdX5yDu3YqKDJbyn3QKiV6P38bYqkhsoDotanurh5mHLRc9hITE+pxL6Kjcpzw==", + "type": "package", + "path": "microsoft.data.sqlite/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/netstandard2.0/_._", + "microsoft.data.sqlite.10.0.3.nupkg.sha512", + "microsoft.data.sqlite.nuspec" + ] + }, + "Microsoft.Data.Sqlite.Core/10.0.3": { + "sha512": "onD94qHlvteS2S9eg1T7Huehm3x92no4nU1AyDWjSmT6jDBhY8QF0UapwNhFA+1dArzxM10fV7s6uI1EIK2+dw==", + "type": "package", + "path": "microsoft.data.sqlite.core/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net8.0/Microsoft.Data.Sqlite.dll", + "lib/net8.0/Microsoft.Data.Sqlite.xml", + "lib/netstandard2.0/Microsoft.Data.Sqlite.dll", + "lib/netstandard2.0/Microsoft.Data.Sqlite.xml", + "microsoft.data.sqlite.core.10.0.3.nupkg.sha512", + "microsoft.data.sqlite.core.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore/10.0.3": { + "sha512": "CDEImwD4A7BseABJMCpLZnhfFjmPY/bHwhhS70elc6gLI/bYUEOhxWt7PmaNGYGhIEzOnStlCy5QcVb+8dod5Q==", + "type": "package", + "path": "microsoft.entityframeworkcore/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props", + "lib/net10.0/Microsoft.EntityFrameworkCore.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.xml", + "microsoft.entityframeworkcore.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Abstractions/10.0.3": { + "sha512": "c7Uoz381xnMHNBRB8eHRhGgzUtXbgddlbODhwZRrTSzZWDharp3RkJsFwhxyESbeXhCqmML7VdvjMQ7uu+HreA==", + "type": "package", + "path": "microsoft.entityframeworkcore.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.xml", + "microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.abstractions.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Analyzers/10.0.3": { + "sha512": "lVABgJTyTUNE7Bi0bSu4dWHiCHIXEGzTh/kLh0N07IgU/tIKwTeBPp8tgV4x2iKj4h7iPLo8oXzyHmLDGtAE1g==", + "type": "package", + "path": "microsoft.entityframeworkcore.analyzers/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "analyzers/dotnet/cs/Microsoft.EntityFrameworkCore.Analyzers.dll", + "docs/PACKAGE.md", + "microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.analyzers.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Relational/10.0.3": { + "sha512": "Pmh60OK9neVr/M0FJwm9hlzm2bD4Kd65SID8E6SP5c90tExNgXwORrlEWl0oGU/ig9ifpNN4PSpIrnHNozlT5w==", + "type": "package", + "path": "microsoft.entityframeworkcore.relational/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.xml", + "microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.relational.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Sqlite/10.0.3": { + "sha512": "pXP9l00ZzsZuS6ajUJFLHi5vt3vVxHaxQXCwRs73tyqNbjPX/4ac114wyfAepMSmUKq0roFFoTZ7h+1K1+iQjQ==", + "type": "package", + "path": "microsoft.entityframeworkcore.sqlite/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/_._", + "microsoft.entityframeworkcore.sqlite.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.sqlite.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Sqlite.Core/10.0.3": { + "sha512": "bUkOkSwPqvhNlsMMg5dA/PR9S4cVehiHEIZklakRH6JZCFsEBNvUz8kBRGK8Hi6mcaPKUAmVZQkN3moZsBYZLA==", + "type": "package", + "path": "microsoft.entityframeworkcore.sqlite.core/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Sqlite.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Sqlite.xml", + "microsoft.entityframeworkcore.sqlite.core.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.sqlite.core.nuspec" + ] + }, + "Microsoft.Extensions.ApiDescription.Server/6.0.5": { + "sha512": "Ckb5EDBUNJdFWyajfXzUIMRkhf52fHZOQuuZg/oiu8y7zDCVwD0iHhew6MnThjHmevanpxL3f5ci2TtHQEN6bw==", + "type": "package", + "path": "microsoft.extensions.apidescription.server/6.0.5", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "build/Microsoft.Extensions.ApiDescription.Server.props", + "build/Microsoft.Extensions.ApiDescription.Server.targets", + "buildMultiTargeting/Microsoft.Extensions.ApiDescription.Server.props", + "buildMultiTargeting/Microsoft.Extensions.ApiDescription.Server.targets", + "microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512", + "microsoft.extensions.apidescription.server.nuspec", + "tools/Newtonsoft.Json.dll", + "tools/dotnet-getdocument.deps.json", + "tools/dotnet-getdocument.dll", + "tools/dotnet-getdocument.runtimeconfig.json", + "tools/net461-x86/GetDocument.Insider.exe", + "tools/net461-x86/GetDocument.Insider.exe.config", + "tools/net461-x86/Microsoft.Win32.Primitives.dll", + "tools/net461-x86/System.AppContext.dll", + "tools/net461-x86/System.Buffers.dll", + "tools/net461-x86/System.Collections.Concurrent.dll", + "tools/net461-x86/System.Collections.NonGeneric.dll", + "tools/net461-x86/System.Collections.Specialized.dll", + "tools/net461-x86/System.Collections.dll", + "tools/net461-x86/System.ComponentModel.EventBasedAsync.dll", + "tools/net461-x86/System.ComponentModel.Primitives.dll", + "tools/net461-x86/System.ComponentModel.TypeConverter.dll", + "tools/net461-x86/System.ComponentModel.dll", + "tools/net461-x86/System.Console.dll", + "tools/net461-x86/System.Data.Common.dll", + "tools/net461-x86/System.Diagnostics.Contracts.dll", + "tools/net461-x86/System.Diagnostics.Debug.dll", + "tools/net461-x86/System.Diagnostics.DiagnosticSource.dll", + "tools/net461-x86/System.Diagnostics.FileVersionInfo.dll", + "tools/net461-x86/System.Diagnostics.Process.dll", + "tools/net461-x86/System.Diagnostics.StackTrace.dll", + "tools/net461-x86/System.Diagnostics.TextWriterTraceListener.dll", + "tools/net461-x86/System.Diagnostics.Tools.dll", + "tools/net461-x86/System.Diagnostics.TraceSource.dll", + "tools/net461-x86/System.Diagnostics.Tracing.dll", + "tools/net461-x86/System.Drawing.Primitives.dll", + "tools/net461-x86/System.Dynamic.Runtime.dll", + "tools/net461-x86/System.Globalization.Calendars.dll", + "tools/net461-x86/System.Globalization.Extensions.dll", + "tools/net461-x86/System.Globalization.dll", + "tools/net461-x86/System.IO.Compression.ZipFile.dll", + "tools/net461-x86/System.IO.Compression.dll", + "tools/net461-x86/System.IO.FileSystem.DriveInfo.dll", + "tools/net461-x86/System.IO.FileSystem.Primitives.dll", + "tools/net461-x86/System.IO.FileSystem.Watcher.dll", + "tools/net461-x86/System.IO.FileSystem.dll", + "tools/net461-x86/System.IO.IsolatedStorage.dll", + "tools/net461-x86/System.IO.MemoryMappedFiles.dll", + "tools/net461-x86/System.IO.Pipes.dll", + "tools/net461-x86/System.IO.UnmanagedMemoryStream.dll", + "tools/net461-x86/System.IO.dll", + "tools/net461-x86/System.Linq.Expressions.dll", + "tools/net461-x86/System.Linq.Parallel.dll", + "tools/net461-x86/System.Linq.Queryable.dll", + "tools/net461-x86/System.Linq.dll", + "tools/net461-x86/System.Memory.dll", + "tools/net461-x86/System.Net.Http.dll", + "tools/net461-x86/System.Net.NameResolution.dll", + "tools/net461-x86/System.Net.NetworkInformation.dll", + "tools/net461-x86/System.Net.Ping.dll", + "tools/net461-x86/System.Net.Primitives.dll", + "tools/net461-x86/System.Net.Requests.dll", + "tools/net461-x86/System.Net.Security.dll", + "tools/net461-x86/System.Net.Sockets.dll", + "tools/net461-x86/System.Net.WebHeaderCollection.dll", + "tools/net461-x86/System.Net.WebSockets.Client.dll", + "tools/net461-x86/System.Net.WebSockets.dll", + "tools/net461-x86/System.Numerics.Vectors.dll", + "tools/net461-x86/System.ObjectModel.dll", + "tools/net461-x86/System.Reflection.Extensions.dll", + "tools/net461-x86/System.Reflection.Primitives.dll", + "tools/net461-x86/System.Reflection.dll", + "tools/net461-x86/System.Resources.Reader.dll", + "tools/net461-x86/System.Resources.ResourceManager.dll", + "tools/net461-x86/System.Resources.Writer.dll", + "tools/net461-x86/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net461-x86/System.Runtime.CompilerServices.VisualC.dll", + "tools/net461-x86/System.Runtime.Extensions.dll", + "tools/net461-x86/System.Runtime.Handles.dll", + "tools/net461-x86/System.Runtime.InteropServices.RuntimeInformation.dll", + "tools/net461-x86/System.Runtime.InteropServices.dll", + "tools/net461-x86/System.Runtime.Numerics.dll", + "tools/net461-x86/System.Runtime.Serialization.Formatters.dll", + "tools/net461-x86/System.Runtime.Serialization.Json.dll", + "tools/net461-x86/System.Runtime.Serialization.Primitives.dll", + "tools/net461-x86/System.Runtime.Serialization.Xml.dll", + "tools/net461-x86/System.Runtime.dll", + "tools/net461-x86/System.Security.Claims.dll", + "tools/net461-x86/System.Security.Cryptography.Algorithms.dll", + "tools/net461-x86/System.Security.Cryptography.Csp.dll", + "tools/net461-x86/System.Security.Cryptography.Encoding.dll", + "tools/net461-x86/System.Security.Cryptography.Primitives.dll", + "tools/net461-x86/System.Security.Cryptography.X509Certificates.dll", + "tools/net461-x86/System.Security.Principal.dll", + "tools/net461-x86/System.Security.SecureString.dll", + "tools/net461-x86/System.Text.Encoding.Extensions.dll", + "tools/net461-x86/System.Text.Encoding.dll", + "tools/net461-x86/System.Text.RegularExpressions.dll", + "tools/net461-x86/System.Threading.Overlapped.dll", + "tools/net461-x86/System.Threading.Tasks.Parallel.dll", + "tools/net461-x86/System.Threading.Tasks.dll", + "tools/net461-x86/System.Threading.Thread.dll", + "tools/net461-x86/System.Threading.ThreadPool.dll", + "tools/net461-x86/System.Threading.Timer.dll", + "tools/net461-x86/System.Threading.dll", + "tools/net461-x86/System.ValueTuple.dll", + "tools/net461-x86/System.Xml.ReaderWriter.dll", + "tools/net461-x86/System.Xml.XDocument.dll", + "tools/net461-x86/System.Xml.XPath.XDocument.dll", + "tools/net461-x86/System.Xml.XPath.dll", + "tools/net461-x86/System.Xml.XmlDocument.dll", + "tools/net461-x86/System.Xml.XmlSerializer.dll", + "tools/net461-x86/netstandard.dll", + "tools/net461/GetDocument.Insider.exe", + "tools/net461/GetDocument.Insider.exe.config", + "tools/net461/Microsoft.Win32.Primitives.dll", + "tools/net461/System.AppContext.dll", + "tools/net461/System.Buffers.dll", + "tools/net461/System.Collections.Concurrent.dll", + "tools/net461/System.Collections.NonGeneric.dll", + "tools/net461/System.Collections.Specialized.dll", + "tools/net461/System.Collections.dll", + "tools/net461/System.ComponentModel.EventBasedAsync.dll", + "tools/net461/System.ComponentModel.Primitives.dll", + "tools/net461/System.ComponentModel.TypeConverter.dll", + "tools/net461/System.ComponentModel.dll", + "tools/net461/System.Console.dll", + "tools/net461/System.Data.Common.dll", + "tools/net461/System.Diagnostics.Contracts.dll", + "tools/net461/System.Diagnostics.Debug.dll", + "tools/net461/System.Diagnostics.DiagnosticSource.dll", + "tools/net461/System.Diagnostics.FileVersionInfo.dll", + "tools/net461/System.Diagnostics.Process.dll", + "tools/net461/System.Diagnostics.StackTrace.dll", + "tools/net461/System.Diagnostics.TextWriterTraceListener.dll", + "tools/net461/System.Diagnostics.Tools.dll", + "tools/net461/System.Diagnostics.TraceSource.dll", + "tools/net461/System.Diagnostics.Tracing.dll", + "tools/net461/System.Drawing.Primitives.dll", + "tools/net461/System.Dynamic.Runtime.dll", + "tools/net461/System.Globalization.Calendars.dll", + "tools/net461/System.Globalization.Extensions.dll", + "tools/net461/System.Globalization.dll", + "tools/net461/System.IO.Compression.ZipFile.dll", + "tools/net461/System.IO.Compression.dll", + "tools/net461/System.IO.FileSystem.DriveInfo.dll", + "tools/net461/System.IO.FileSystem.Primitives.dll", + "tools/net461/System.IO.FileSystem.Watcher.dll", + "tools/net461/System.IO.FileSystem.dll", + "tools/net461/System.IO.IsolatedStorage.dll", + "tools/net461/System.IO.MemoryMappedFiles.dll", + "tools/net461/System.IO.Pipes.dll", + "tools/net461/System.IO.UnmanagedMemoryStream.dll", + "tools/net461/System.IO.dll", + "tools/net461/System.Linq.Expressions.dll", + "tools/net461/System.Linq.Parallel.dll", + "tools/net461/System.Linq.Queryable.dll", + "tools/net461/System.Linq.dll", + "tools/net461/System.Memory.dll", + "tools/net461/System.Net.Http.dll", + "tools/net461/System.Net.NameResolution.dll", + "tools/net461/System.Net.NetworkInformation.dll", + "tools/net461/System.Net.Ping.dll", + "tools/net461/System.Net.Primitives.dll", + "tools/net461/System.Net.Requests.dll", + "tools/net461/System.Net.Security.dll", + "tools/net461/System.Net.Sockets.dll", + "tools/net461/System.Net.WebHeaderCollection.dll", + "tools/net461/System.Net.WebSockets.Client.dll", + "tools/net461/System.Net.WebSockets.dll", + "tools/net461/System.Numerics.Vectors.dll", + "tools/net461/System.ObjectModel.dll", + "tools/net461/System.Reflection.Extensions.dll", + "tools/net461/System.Reflection.Primitives.dll", + "tools/net461/System.Reflection.dll", + "tools/net461/System.Resources.Reader.dll", + "tools/net461/System.Resources.ResourceManager.dll", + "tools/net461/System.Resources.Writer.dll", + "tools/net461/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net461/System.Runtime.CompilerServices.VisualC.dll", + "tools/net461/System.Runtime.Extensions.dll", + "tools/net461/System.Runtime.Handles.dll", + "tools/net461/System.Runtime.InteropServices.RuntimeInformation.dll", + "tools/net461/System.Runtime.InteropServices.dll", + "tools/net461/System.Runtime.Numerics.dll", + "tools/net461/System.Runtime.Serialization.Formatters.dll", + "tools/net461/System.Runtime.Serialization.Json.dll", + "tools/net461/System.Runtime.Serialization.Primitives.dll", + "tools/net461/System.Runtime.Serialization.Xml.dll", + "tools/net461/System.Runtime.dll", + "tools/net461/System.Security.Claims.dll", + "tools/net461/System.Security.Cryptography.Algorithms.dll", + "tools/net461/System.Security.Cryptography.Csp.dll", + "tools/net461/System.Security.Cryptography.Encoding.dll", + "tools/net461/System.Security.Cryptography.Primitives.dll", + "tools/net461/System.Security.Cryptography.X509Certificates.dll", + "tools/net461/System.Security.Principal.dll", + "tools/net461/System.Security.SecureString.dll", + "tools/net461/System.Text.Encoding.Extensions.dll", + "tools/net461/System.Text.Encoding.dll", + "tools/net461/System.Text.RegularExpressions.dll", + "tools/net461/System.Threading.Overlapped.dll", + "tools/net461/System.Threading.Tasks.Parallel.dll", + "tools/net461/System.Threading.Tasks.dll", + "tools/net461/System.Threading.Thread.dll", + "tools/net461/System.Threading.ThreadPool.dll", + "tools/net461/System.Threading.Timer.dll", + "tools/net461/System.Threading.dll", + "tools/net461/System.ValueTuple.dll", + "tools/net461/System.Xml.ReaderWriter.dll", + "tools/net461/System.Xml.XDocument.dll", + "tools/net461/System.Xml.XPath.XDocument.dll", + "tools/net461/System.Xml.XPath.dll", + "tools/net461/System.Xml.XmlDocument.dll", + "tools/net461/System.Xml.XmlSerializer.dll", + "tools/net461/netstandard.dll", + "tools/netcoreapp2.1/GetDocument.Insider.deps.json", + "tools/netcoreapp2.1/GetDocument.Insider.dll", + "tools/netcoreapp2.1/GetDocument.Insider.runtimeconfig.json", + "tools/netcoreapp2.1/System.Diagnostics.DiagnosticSource.dll" + ] + }, + "Microsoft.Extensions.Caching.Abstractions/10.0.3": { + "sha512": "5dtXBvI8t3z8pF4tB38JYgi/enCL/DwSXxpqShgFz3SHJ7IzqFIMs6Gu5ik8sNZzcO9qQs3xIDpB3vDamkYG+Q==", + "type": "package", + "path": "microsoft.extensions.caching.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Caching.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Caching.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.xml", + "microsoft.extensions.caching.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.caching.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Caching.Memory/10.0.3": { + "sha512": "No4fVh0z30SWqiWFRoA4PNdrEco6OjXvCqRFvlmRgDQqqks2bRDdeavUgWEiAX153ZAwW9loUgbxcvuP4NKQLg==", + "type": "package", + "path": "microsoft.extensions.caching.memory/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Caching.Memory.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Caching.Memory.targets", + "lib/net10.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net10.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/net462/Microsoft.Extensions.Caching.Memory.dll", + "lib/net462/Microsoft.Extensions.Caching.Memory.xml", + "lib/net8.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net8.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/net9.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net9.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.xml", + "microsoft.extensions.caching.memory.10.0.3.nupkg.sha512", + "microsoft.extensions.caching.memory.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Configuration/10.0.3": { + "sha512": "H1Cjv2xmm7O3iAGmFTcnSM0ZhLQ/7SqefmAvSJoT1PbXoxeYc2fo0mCLn2JlVbr9E6YpoU9q/o0fI9neDJB0xQ==", + "type": "package", + "path": "microsoft.extensions.configuration/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Configuration.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Configuration.targets", + "lib/net10.0/Microsoft.Extensions.Configuration.dll", + "lib/net10.0/Microsoft.Extensions.Configuration.xml", + "lib/net462/Microsoft.Extensions.Configuration.dll", + "lib/net462/Microsoft.Extensions.Configuration.xml", + "lib/net8.0/Microsoft.Extensions.Configuration.dll", + "lib/net8.0/Microsoft.Extensions.Configuration.xml", + "lib/net9.0/Microsoft.Extensions.Configuration.dll", + "lib/net9.0/Microsoft.Extensions.Configuration.xml", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.dll", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.xml", + "microsoft.extensions.configuration.10.0.3.nupkg.sha512", + "microsoft.extensions.configuration.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Configuration.Abstractions/10.0.3": { + "sha512": "xVDHL0+SIgemfh95fTO9cGLe17TWv/ZP0n7m01z8X6pzt2DmQpucioWR/mYZA1sRlkWnkXzfl0JweLNWmE9WMg==", + "type": "package", + "path": "microsoft.extensions.configuration.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Configuration.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Configuration.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "microsoft.extensions.configuration.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.configuration.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Configuration.Binder/10.0.3": { + "sha512": "759UhpKaR5Jsll9kXpkft4z/7tpeF7Dw2rTY/9f9JchaSQTpRFNIPkZFZvoo7fFpbjUaqtDlO5aiGpmQrp/EUA==", + "type": "package", + "path": "microsoft.extensions.configuration.binder/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/cs/Microsoft.Extensions.Configuration.Binder.SourceGeneration.dll", + "analyzers/dotnet/cs/cs/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/de/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/es/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/fr/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/it/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/ja/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/ko/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/pl/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/pt-BR/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/ru/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/tr/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/zh-Hans/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/zh-Hant/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets", + "lib/net10.0/Microsoft.Extensions.Configuration.Binder.dll", + "lib/net10.0/Microsoft.Extensions.Configuration.Binder.xml", + "lib/net462/Microsoft.Extensions.Configuration.Binder.dll", + "lib/net462/Microsoft.Extensions.Configuration.Binder.xml", + "lib/net8.0/Microsoft.Extensions.Configuration.Binder.dll", + "lib/net8.0/Microsoft.Extensions.Configuration.Binder.xml", + "lib/net9.0/Microsoft.Extensions.Configuration.Binder.dll", + "lib/net9.0/Microsoft.Extensions.Configuration.Binder.xml", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Binder.dll", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Binder.xml", + "microsoft.extensions.configuration.binder.10.0.3.nupkg.sha512", + "microsoft.extensions.configuration.binder.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyInjection/10.0.3": { + "sha512": "2DLOmC0EkB2smVK8lPP1PIKEgL1arE3CMp9XSIQB/Y7ev5nnnyuM/PizKJ6QfLD08QCYoopSC9SFdbYglDomYg==", + "type": "package", + "path": "microsoft.extensions.dependencyinjection/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyInjection.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.targets", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/net462/Microsoft.Extensions.DependencyInjection.dll", + "lib/net462/Microsoft.Extensions.DependencyInjection.xml", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.dll", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.xml", + "microsoft.extensions.dependencyinjection.10.0.3.nupkg.sha512", + "microsoft.extensions.dependencyinjection.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/10.0.3": { + "sha512": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==", + "type": "package", + "path": "microsoft.extensions.dependencyinjection.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyInjection.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "microsoft.extensions.dependencyinjection.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.dependencyinjection.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyModel/10.0.3": { + "sha512": "31kRjr1fgdJO1UZ/AsjL2noqwht+juHMQ8c/oh8CEsDhlM2+0zwVZVsZjxSfOFiPtn5+6kRGuvSbLAufAPT0kA==", + "type": "package", + "path": "microsoft.extensions.dependencymodel/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyModel.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyModel.targets", + "lib/net10.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net10.0/Microsoft.Extensions.DependencyModel.xml", + "lib/net462/Microsoft.Extensions.DependencyModel.dll", + "lib/net462/Microsoft.Extensions.DependencyModel.xml", + "lib/net8.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net8.0/Microsoft.Extensions.DependencyModel.xml", + "lib/net9.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net9.0/Microsoft.Extensions.DependencyModel.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyModel.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyModel.xml", + "microsoft.extensions.dependencymodel.10.0.3.nupkg.sha512", + "microsoft.extensions.dependencymodel.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Diagnostics/10.0.3": { + "sha512": "tc0R6i2T+138taoxFPQXb7Sy/4rtq4ytoJrAt4fNGs6k89mHpEhZnXUNgaFKwwb5Ud5rIUeLC6tfpsuHNwiWqg==", + "type": "package", + "path": "microsoft.extensions.diagnostics/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Diagnostics.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Diagnostics.targets", + "lib/net10.0/Microsoft.Extensions.Diagnostics.dll", + "lib/net10.0/Microsoft.Extensions.Diagnostics.xml", + "lib/net462/Microsoft.Extensions.Diagnostics.dll", + "lib/net462/Microsoft.Extensions.Diagnostics.xml", + "lib/net8.0/Microsoft.Extensions.Diagnostics.dll", + "lib/net8.0/Microsoft.Extensions.Diagnostics.xml", + "lib/net9.0/Microsoft.Extensions.Diagnostics.dll", + "lib/net9.0/Microsoft.Extensions.Diagnostics.xml", + "lib/netstandard2.0/Microsoft.Extensions.Diagnostics.dll", + "lib/netstandard2.0/Microsoft.Extensions.Diagnostics.xml", + "microsoft.extensions.diagnostics.10.0.3.nupkg.sha512", + "microsoft.extensions.diagnostics.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Diagnostics.Abstractions/10.0.3": { + "sha512": "mQiTzAj7PIJ2A9YXR5QhgulS1fTWhmQc3ckd1Mrf3hKW07d03fBDqx8vVaFw+cRTebDOeB6pNqdWdnRxsi1hBA==", + "type": "package", + "path": "microsoft.extensions.diagnostics.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Diagnostics.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Diagnostics.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Diagnostics.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Diagnostics.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Diagnostics.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Diagnostics.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Diagnostics.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Diagnostics.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Diagnostics.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Diagnostics.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Diagnostics.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Diagnostics.Abstractions.xml", + "microsoft.extensions.diagnostics.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.diagnostics.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.FileProviders.Abstractions/10.0.3": { + "sha512": "4TD9AXDRsipTmaemwnjt/DM5Ri0de2JzHQhvZ4woBTjUtL4XrPNsMrOk5oiLJAx1gTrE6pOIhxv+lEde5F6CZA==", + "type": "package", + "path": "microsoft.extensions.fileproviders.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.FileProviders.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.FileProviders.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.FileProviders.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.FileProviders.Abstractions.xml", + "lib/net462/Microsoft.Extensions.FileProviders.Abstractions.dll", + "lib/net462/Microsoft.Extensions.FileProviders.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.FileProviders.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.FileProviders.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.FileProviders.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.FileProviders.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.FileProviders.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.FileProviders.Abstractions.xml", + "microsoft.extensions.fileproviders.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.fileproviders.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Hosting.Abstractions/10.0.3": { + "sha512": "GdMpC10Jf6poxSvUJ4lgYpJ5F/kJeaAoJmrPufjBoPYyCTKKY5Dyl0rZA+LBNvFqTq1cZa/lhlptlUhNvU6xrg==", + "type": "package", + "path": "microsoft.extensions.hosting.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Hosting.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Hosting.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Hosting.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Hosting.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Hosting.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Hosting.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Hosting.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Hosting.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Hosting.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Hosting.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Hosting.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Hosting.Abstractions.xml", + "lib/netstandard2.1/Microsoft.Extensions.Hosting.Abstractions.dll", + "lib/netstandard2.1/Microsoft.Extensions.Hosting.Abstractions.xml", + "microsoft.extensions.hosting.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.hosting.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Http/10.0.3": { + "sha512": "M5gWob3dtzlF14oto1lR1ZuSJrR0gGc+obv7zY9LGmX5y3Ndpve29MrrjqJW/m4CFud4TE/KFUuHjjtwxhCO8g==", + "type": "package", + "path": "microsoft.extensions.http/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Http.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Http.targets", + "lib/net10.0/Microsoft.Extensions.Http.dll", + "lib/net10.0/Microsoft.Extensions.Http.xml", + "lib/net462/Microsoft.Extensions.Http.dll", + "lib/net462/Microsoft.Extensions.Http.xml", + "lib/net8.0/Microsoft.Extensions.Http.dll", + "lib/net8.0/Microsoft.Extensions.Http.xml", + "lib/net9.0/Microsoft.Extensions.Http.dll", + "lib/net9.0/Microsoft.Extensions.Http.xml", + "lib/netstandard2.0/Microsoft.Extensions.Http.dll", + "lib/netstandard2.0/Microsoft.Extensions.Http.xml", + "microsoft.extensions.http.10.0.3.nupkg.sha512", + "microsoft.extensions.http.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Logging/10.0.3": { + "sha512": "8D9Er1cGXNjNDIB+VLBNHn386L5ls2FoiG9a6o12gyn+GG3w6jdfUhzT8dtBnKcevE7/fsVA8MS3FBgFfClFtQ==", + "type": "package", + "path": "microsoft.extensions.logging/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Logging.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Logging.targets", + "lib/net10.0/Microsoft.Extensions.Logging.dll", + "lib/net10.0/Microsoft.Extensions.Logging.xml", + "lib/net462/Microsoft.Extensions.Logging.dll", + "lib/net462/Microsoft.Extensions.Logging.xml", + "lib/net8.0/Microsoft.Extensions.Logging.dll", + "lib/net8.0/Microsoft.Extensions.Logging.xml", + "lib/net9.0/Microsoft.Extensions.Logging.dll", + "lib/net9.0/Microsoft.Extensions.Logging.xml", + "lib/netstandard2.0/Microsoft.Extensions.Logging.dll", + "lib/netstandard2.0/Microsoft.Extensions.Logging.xml", + "lib/netstandard2.1/Microsoft.Extensions.Logging.dll", + "lib/netstandard2.1/Microsoft.Extensions.Logging.xml", + "microsoft.extensions.logging.10.0.3.nupkg.sha512", + "microsoft.extensions.logging.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Logging.Abstractions/10.0.3": { + "sha512": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", + "type": "package", + "path": "microsoft.extensions.logging.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn3.11/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn3.11/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn4.0/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "buildTransitive/net461/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/net462/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.xml", + "microsoft.extensions.logging.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.logging.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Options/10.0.3": { + "sha512": "hU6WzGTPvPoLA2ng1ILvWQb3g0qORdlHNsxI8IcPLumJb3suimYUl+bbDzdo1V4KFsvVhnMWzysHpKbZaoDQPQ==", + "type": "package", + "path": "microsoft.extensions.options/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Options.SourceGeneration.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "buildTransitive/net461/Microsoft.Extensions.Options.targets", + "buildTransitive/net462/Microsoft.Extensions.Options.targets", + "buildTransitive/net8.0/Microsoft.Extensions.Options.targets", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Options.targets", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Options.targets", + "lib/net10.0/Microsoft.Extensions.Options.dll", + "lib/net10.0/Microsoft.Extensions.Options.xml", + "lib/net462/Microsoft.Extensions.Options.dll", + "lib/net462/Microsoft.Extensions.Options.xml", + "lib/net8.0/Microsoft.Extensions.Options.dll", + "lib/net8.0/Microsoft.Extensions.Options.xml", + "lib/net9.0/Microsoft.Extensions.Options.dll", + "lib/net9.0/Microsoft.Extensions.Options.xml", + "lib/netstandard2.0/Microsoft.Extensions.Options.dll", + "lib/netstandard2.0/Microsoft.Extensions.Options.xml", + "lib/netstandard2.1/Microsoft.Extensions.Options.dll", + "lib/netstandard2.1/Microsoft.Extensions.Options.xml", + "microsoft.extensions.options.10.0.3.nupkg.sha512", + "microsoft.extensions.options.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Options.ConfigurationExtensions/10.0.3": { + "sha512": "bn6QoBbbvwmzLIFyxrnL2/e+sqoNUOGbHyfWK9DPONMv1mDCYHm/C7MusYASM31b2lUx6OiDmonb3v+dv5t0nA==", + "type": "package", + "path": "microsoft.extensions.options.configurationextensions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Options.ConfigurationExtensions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Options.ConfigurationExtensions.targets", + "lib/net10.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll", + "lib/net10.0/Microsoft.Extensions.Options.ConfigurationExtensions.xml", + "lib/net462/Microsoft.Extensions.Options.ConfigurationExtensions.dll", + "lib/net462/Microsoft.Extensions.Options.ConfigurationExtensions.xml", + "lib/net8.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll", + "lib/net8.0/Microsoft.Extensions.Options.ConfigurationExtensions.xml", + "lib/net9.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll", + "lib/net9.0/Microsoft.Extensions.Options.ConfigurationExtensions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Options.ConfigurationExtensions.xml", + "microsoft.extensions.options.configurationextensions.10.0.3.nupkg.sha512", + "microsoft.extensions.options.configurationextensions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Primitives/10.0.3": { + "sha512": "GEcpTwo7sUoLGGNTqV1FZEuL+tTD9m81NX/mh099dqGNna07/UGZShKQNZRw4hv6nlliSUwYQgSYc7OR99Jufg==", + "type": "package", + "path": "microsoft.extensions.primitives/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Primitives.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Primitives.targets", + "lib/net10.0/Microsoft.Extensions.Primitives.dll", + "lib/net10.0/Microsoft.Extensions.Primitives.xml", + "lib/net462/Microsoft.Extensions.Primitives.dll", + "lib/net462/Microsoft.Extensions.Primitives.xml", + "lib/net8.0/Microsoft.Extensions.Primitives.dll", + "lib/net8.0/Microsoft.Extensions.Primitives.xml", + "lib/net9.0/Microsoft.Extensions.Primitives.dll", + "lib/net9.0/Microsoft.Extensions.Primitives.xml", + "lib/netstandard2.0/Microsoft.Extensions.Primitives.dll", + "lib/netstandard2.0/Microsoft.Extensions.Primitives.xml", + "microsoft.extensions.primitives.10.0.3.nupkg.sha512", + "microsoft.extensions.primitives.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.OpenApi/1.6.22": { + "sha512": "aBvunmrdu/x+4CaA/UP1Jx4xWGwk4kymhoIRnn2Vp+zi5/KOPQJ9EkSXHRUr01WcGKtYl3Au7XfkPJbU1G2sjQ==", + "type": "package", + "path": "microsoft.openapi/1.6.22", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/netstandard2.0/Microsoft.OpenApi.dll", + "lib/netstandard2.0/Microsoft.OpenApi.pdb", + "lib/netstandard2.0/Microsoft.OpenApi.xml", + "microsoft.openapi.1.6.22.nupkg.sha512", + "microsoft.openapi.nuspec" + ] + }, + "Microsoft.Win32.SystemEvents/9.0.2": { + "sha512": "5BkGZ6mHp2dHydR29sb0fDfAuqkv30AHtTih8wMzvPZysOmBFvHfnkR2w3tsc0pSiIg8ZoKyefJXWy9r3pBh0w==", + "type": "package", + "path": "microsoft.win32.systemevents/9.0.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Win32.SystemEvents.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Win32.SystemEvents.targets", + "lib/net462/Microsoft.Win32.SystemEvents.dll", + "lib/net462/Microsoft.Win32.SystemEvents.xml", + "lib/net8.0/Microsoft.Win32.SystemEvents.dll", + "lib/net8.0/Microsoft.Win32.SystemEvents.xml", + "lib/net9.0/Microsoft.Win32.SystemEvents.dll", + "lib/net9.0/Microsoft.Win32.SystemEvents.xml", + "lib/netstandard2.0/Microsoft.Win32.SystemEvents.dll", + "lib/netstandard2.0/Microsoft.Win32.SystemEvents.xml", + "microsoft.win32.systemevents.9.0.2.nupkg.sha512", + "microsoft.win32.systemevents.nuspec", + "runtimes/win/lib/net8.0/Microsoft.Win32.SystemEvents.dll", + "runtimes/win/lib/net8.0/Microsoft.Win32.SystemEvents.xml", + "runtimes/win/lib/net9.0/Microsoft.Win32.SystemEvents.dll", + "runtimes/win/lib/net9.0/Microsoft.Win32.SystemEvents.xml", + "useSharedDesignerContext.txt" + ] + }, + "NEbml/1.1.0.5": { + "sha512": "svtqDc+hue9kbnqNN2KkK4om/hDrc7K127cNb5FIYfgKgzo+JNDPXNLp8NioCchHhBO3lxWd4Cp/iiZZ3aoUqg==", + "type": "package", + "path": "nebml/1.1.0.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net461/NEbml.Core.dll", + "lib/net461/NEbml.Core.xml", + "lib/netstandard2.0/NEbml.Core.dll", + "lib/netstandard2.0/NEbml.Core.xml", + "nebml.1.1.0.5.nupkg.sha512", + "nebml.nuspec" + ] + }, + "Newtonsoft.Json/13.0.4": { + "sha512": "pdgNNMai3zv51W5aq268sujXUyx7SNdE2bj1wZcWjAQrKMFZV260lbqYop1d2GM67JI1huLRwxo9ZqnfF/lC6A==", + "type": "package", + "path": "newtonsoft.json/13.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.md", + "README.md", + "lib/net20/Newtonsoft.Json.dll", + "lib/net20/Newtonsoft.Json.xml", + "lib/net35/Newtonsoft.Json.dll", + "lib/net35/Newtonsoft.Json.xml", + "lib/net40/Newtonsoft.Json.dll", + "lib/net40/Newtonsoft.Json.xml", + "lib/net45/Newtonsoft.Json.dll", + "lib/net45/Newtonsoft.Json.xml", + "lib/net6.0/Newtonsoft.Json.dll", + "lib/net6.0/Newtonsoft.Json.xml", + "lib/netstandard1.0/Newtonsoft.Json.dll", + "lib/netstandard1.0/Newtonsoft.Json.xml", + "lib/netstandard1.3/Newtonsoft.Json.dll", + "lib/netstandard1.3/Newtonsoft.Json.xml", + "lib/netstandard2.0/Newtonsoft.Json.dll", + "lib/netstandard2.0/Newtonsoft.Json.xml", + "newtonsoft.json.13.0.4.nupkg.sha512", + "newtonsoft.json.nuspec", + "packageIcon.png" + ] + }, + "PlaylistsNET/1.4.1": { + "sha512": "GmDShQkKK08YJD94rOoWebe3M2QRk6XZkMcPL80ZcRnFs255kifAJt7I9n6mmSlcQc/BRpgiY3jHDIOxIqA/cA==", + "type": "package", + "path": "playlistsnet/1.4.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net48/PlaylistsNET.dll", + "lib/net6.0/PlaylistsNET.dll", + "lib/net8.0/PlaylistsNET.dll", + "lib/netstandard1.4/PlaylistsNET.dll", + "lib/netstandard2.0/PlaylistsNET.dll", + "playlistsnet.1.4.1.nupkg.sha512", + "playlistsnet.nuspec" + ] + }, + "Polly/8.6.5": { + "sha512": "VqtW2ZE/ALvQMAH1cQY3qZ2cF2OXa3oe/HKMdOv6Q02HCoEW0rsFNfcBONXlHBe1TnjWW1vdRxBEkPeq0/2FHA==", + "type": "package", + "path": "polly/8.6.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "lib/net462/Polly.dll", + "lib/net462/Polly.pdb", + "lib/net462/Polly.xml", + "lib/net472/Polly.dll", + "lib/net472/Polly.pdb", + "lib/net472/Polly.xml", + "lib/net6.0/Polly.dll", + "lib/net6.0/Polly.pdb", + "lib/net6.0/Polly.xml", + "lib/netstandard2.0/Polly.dll", + "lib/netstandard2.0/Polly.pdb", + "lib/netstandard2.0/Polly.xml", + "package-icon.png", + "package-readme.md", + "polly.8.6.5.nupkg.sha512", + "polly.nuspec" + ] + }, + "Polly.Core/8.6.5": { + "sha512": "t+sUVrIwvo7UmsgHGgOG9F0GDZSRIm47u2ylH17Gvcv1q5hNEwgD5GoBlFyc0kh/pebmPyrAgvGsR/65ZBaXlg==", + "type": "package", + "path": "polly.core/8.6.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "lib/net462/Polly.Core.dll", + "lib/net462/Polly.Core.pdb", + "lib/net462/Polly.Core.xml", + "lib/net472/Polly.Core.dll", + "lib/net472/Polly.Core.pdb", + "lib/net472/Polly.Core.xml", + "lib/net6.0/Polly.Core.dll", + "lib/net6.0/Polly.Core.pdb", + "lib/net6.0/Polly.Core.xml", + "lib/net8.0/Polly.Core.dll", + "lib/net8.0/Polly.Core.pdb", + "lib/net8.0/Polly.Core.xml", + "lib/netstandard2.0/Polly.Core.dll", + "lib/netstandard2.0/Polly.Core.pdb", + "lib/netstandard2.0/Polly.Core.xml", + "package-icon.png", + "package-readme.md", + "polly.core.8.6.5.nupkg.sha512", + "polly.core.nuspec" + ] + }, + "prometheus-net/3.1.2": { + "sha512": "NbvHlbPtQAXxYyxswZLodUhoqWFH8golC4JGllrUcWvNffC+T59aDzLmTHx00mmVQbQsGlslNPVkYGl1tD5mZg==", + "type": "package", + "path": "prometheus-net/3.1.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Prometheus.NetStandard.deps.json", + "lib/netstandard2.0/Prometheus.NetStandard.dll", + "lib/netstandard2.0/Prometheus.NetStandard.pdb", + "lib/netstandard2.0/Prometheus.NetStandard.xml", + "prometheus-net.3.1.2.nupkg.sha512", + "prometheus-net.nuspec" + ] + }, + "prometheus-net.DotNetRuntime/4.4.1": { + "sha512": "TGVjy3qhl4Gb6x2aJeZIk/e5bM98YnDGBn1uUxFNdw9268VDUTVpI62lt9162BM/ZH8B9hmaTtgbWZN8wqwDPg==", + "type": "package", + "path": "prometheus-net.dotnetruntime/4.4.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net5.0/prometheus-net.DotNetRuntime.dll", + "lib/net5.0/prometheus-net.DotNetRuntime.pdb", + "lib/net5.0/prometheus-net.DotNetRuntime.xml", + "lib/net6.0/prometheus-net.DotNetRuntime.dll", + "lib/net6.0/prometheus-net.DotNetRuntime.pdb", + "lib/net6.0/prometheus-net.DotNetRuntime.xml", + "lib/netcoreapp3.1/prometheus-net.DotNetRuntime.dll", + "lib/netcoreapp3.1/prometheus-net.DotNetRuntime.pdb", + "lib/netcoreapp3.1/prometheus-net.DotNetRuntime.xml", + "lib/netstandard2.1/prometheus-net.DotNetRuntime.dll", + "lib/netstandard2.1/prometheus-net.DotNetRuntime.pdb", + "lib/netstandard2.1/prometheus-net.DotNetRuntime.xml", + "prometheus-net.dotnetruntime.4.4.1.nupkg.sha512", + "prometheus-net.dotnetruntime.nuspec" + ] + }, + "SerilogAnalyzer/0.15.0": { + "sha512": "sVpwfls4MfNnwIXLSGCgaUnV+c9kgJ8ia6GsyRcpd4Vs3gLogSDtSYBYrre2K2u/PNMo8GgG09RehwVnze70Tw==", + "type": "package", + "path": "seriloganalyzer/0.15.0", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/SerilogAnalyzer.dll", + "seriloganalyzer.0.15.0.nupkg.sha512", + "seriloganalyzer.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "SmartAnalyzers.MultithreadingAnalyzer/1.1.31": { + "sha512": "2f2k7bbhDd132ArglCKpzKoWBcp3uzbIFcb4aosnlqIKlfYKDE2HevBVRNVa+LkWFnjXFFWs47Bo96fu8iS//Q==", + "type": "package", + "path": "smartanalyzers.multithreadinganalyzer/1.1.31", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/SmartAnalyzers.MultithreadingAnalyzer.dll", + "smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512", + "smartanalyzers.multithreadinganalyzer.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "SQLitePCLRaw.bundle_e_sqlite3/2.1.11": { + "sha512": "DC4nA7yWnf4UZdgJDF+9Mus4/cb0Y3Sfgi3gDnAoKNAIBwzkskNAbNbyu+u4atT0ruVlZNJfwZmwiEwE5oz9LQ==", + "type": "package", + "path": "sqlitepclraw.bundle_e_sqlite3/2.1.11", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/monoandroid90/SQLitePCLRaw.batteries_v2.dll", + "lib/net461/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-android31.0/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-android31.0/SQLitePCLRaw.batteries_v2.xml", + "lib/net6.0-ios14.0/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-ios14.2/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-tvos10.0/SQLitePCLRaw.batteries_v2.dll", + "lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll", + "lib/xamarinios10/SQLitePCLRaw.batteries_v2.dll", + "sqlitepclraw.bundle_e_sqlite3.2.1.11.nupkg.sha512", + "sqlitepclraw.bundle_e_sqlite3.nuspec" + ] + }, + "SQLitePCLRaw.core/2.1.11": { + "sha512": "PK0GLFkfhZzLQeR3PJf71FmhtHox+U3vcY6ZtswoMjrefkB9k6ErNJEnwXqc5KgXDSjige2XXrezqS39gkpQKA==", + "type": "package", + "path": "sqlitepclraw.core/2.1.11", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/SQLitePCLRaw.core.dll", + "sqlitepclraw.core.2.1.11.nupkg.sha512", + "sqlitepclraw.core.nuspec" + ] + }, + "SQLitePCLRaw.lib.e_sqlite3/2.1.11": { + "sha512": "Ev2ytaXiOlWZ4b3R67GZBsemTINslLD1DCJr2xiacpn4tbapu0Q4dHEzSvZSMnVWeE5nlObU3VZN2p81q3XOYQ==", + "type": "package", + "path": "sqlitepclraw.lib.e_sqlite3/2.1.11", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "buildTransitive/net461/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net6.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net7.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net8.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net9.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "lib/net461/_._", + "lib/netstandard2.0/_._", + "runtimes/browser-wasm/nativeassets/net6.0/e_sqlite3.a", + "runtimes/browser-wasm/nativeassets/net7.0/e_sqlite3.a", + "runtimes/browser-wasm/nativeassets/net8.0/e_sqlite3.a", + "runtimes/browser-wasm/nativeassets/net9.0/e_sqlite3.a", + "runtimes/linux-arm/native/libe_sqlite3.so", + "runtimes/linux-arm64/native/libe_sqlite3.so", + "runtimes/linux-armel/native/libe_sqlite3.so", + "runtimes/linux-mips64/native/libe_sqlite3.so", + "runtimes/linux-musl-arm/native/libe_sqlite3.so", + "runtimes/linux-musl-arm64/native/libe_sqlite3.so", + "runtimes/linux-musl-riscv64/native/libe_sqlite3.so", + "runtimes/linux-musl-s390x/native/libe_sqlite3.so", + "runtimes/linux-musl-x64/native/libe_sqlite3.so", + "runtimes/linux-ppc64le/native/libe_sqlite3.so", + "runtimes/linux-riscv64/native/libe_sqlite3.so", + "runtimes/linux-s390x/native/libe_sqlite3.so", + "runtimes/linux-x64/native/libe_sqlite3.so", + "runtimes/linux-x86/native/libe_sqlite3.so", + "runtimes/maccatalyst-arm64/native/libe_sqlite3.dylib", + "runtimes/maccatalyst-x64/native/libe_sqlite3.dylib", + "runtimes/osx-arm64/native/libe_sqlite3.dylib", + "runtimes/osx-x64/native/libe_sqlite3.dylib", + "runtimes/win-arm/native/e_sqlite3.dll", + "runtimes/win-arm64/native/e_sqlite3.dll", + "runtimes/win-x64/native/e_sqlite3.dll", + "runtimes/win-x86/native/e_sqlite3.dll", + "runtimes/win10-arm/nativeassets/uap10.0/e_sqlite3.dll", + "runtimes/win10-arm64/nativeassets/uap10.0/e_sqlite3.dll", + "runtimes/win10-x64/nativeassets/uap10.0/e_sqlite3.dll", + "runtimes/win10-x86/nativeassets/uap10.0/e_sqlite3.dll", + "sqlitepclraw.lib.e_sqlite3.2.1.11.nupkg.sha512", + "sqlitepclraw.lib.e_sqlite3.nuspec" + ] + }, + "SQLitePCLRaw.provider.e_sqlite3/2.1.11": { + "sha512": "Y/0ZkR+r0Cg3DQFuCl1RBnv/tmxpIZRU3HUvelPw6MVaKHwYYR8YNvgs0vuNuXCMvlyJ+Fh88U1D4tah1tt6qw==", + "type": "package", + "path": "sqlitepclraw.provider.e_sqlite3/2.1.11", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net6.0-windows7.0/SQLitePCLRaw.provider.e_sqlite3.dll", + "lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll", + "lib/netstandard2.0/SQLitePCLRaw.provider.e_sqlite3.dll", + "sqlitepclraw.provider.e_sqlite3.2.1.11.nupkg.sha512", + "sqlitepclraw.provider.e_sqlite3.nuspec" + ] + }, + "StyleCop.Analyzers/1.2.0-beta.556": { + "sha512": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", + "type": "package", + "path": "stylecop.analyzers/1.2.0-beta.556", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "THIRD-PARTY-NOTICES.txt", + "stylecop.analyzers.1.2.0-beta.556.nupkg.sha512", + "stylecop.analyzers.nuspec" + ] + }, + "StyleCop.Analyzers.Unstable/1.2.0.556": { + "sha512": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==", + "type": "package", + "path": "stylecop.analyzers.unstable/1.2.0.556", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "THIRD-PARTY-NOTICES.txt", + "analyzers/dotnet/cs/StyleCop.Analyzers.CodeFixes.dll", + "analyzers/dotnet/cs/StyleCop.Analyzers.dll", + "analyzers/dotnet/cs/de-DE/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/en-GB/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/es-MX/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/fr-FR/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/pl-PL/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/pt-BR/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/ru-RU/StyleCop.Analyzers.resources.dll", + "rulesets/StyleCopAnalyzersDefault.ruleset", + "stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512", + "stylecop.analyzers.unstable.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Swashbuckle.AspNetCore/7.3.2": { + "sha512": "WEkW7Cl8bexT75eURuqrqM/TQx+ftWKOWlCHzmw3Fl3oAmxiBO/lDVOuuqZsaBCHQIBcZxA6BbyiG5dv2mD2tw==", + "type": "package", + "path": "swashbuckle.aspnetcore/7.3.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/Swashbuckle.AspNetCore.props", + "buildMultiTargeting/Swashbuckle.AspNetCore.props", + "docs/package-readme.md", + "swashbuckle.aspnetcore.7.3.2.nupkg.sha512", + "swashbuckle.aspnetcore.nuspec" + ] + }, + "Swashbuckle.AspNetCore.ReDoc/6.9.0": { + "sha512": "E/KCx9LRDgWD7vr+BHyLdC2dZMuLhwOMp4xjuJOJChoeWQYVpnOYRdQngeZxGFYGxiahEJ2B1fCvL3XTvzArOQ==", + "type": "package", + "path": "swashbuckle.aspnetcore.redoc/6.9.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net5.0/Swashbuckle.AspNetCore.ReDoc.dll", + "lib/net5.0/Swashbuckle.AspNetCore.ReDoc.pdb", + "lib/net5.0/Swashbuckle.AspNetCore.ReDoc.xml", + "lib/net6.0/Swashbuckle.AspNetCore.ReDoc.dll", + "lib/net6.0/Swashbuckle.AspNetCore.ReDoc.pdb", + "lib/net6.0/Swashbuckle.AspNetCore.ReDoc.xml", + "lib/net7.0/Swashbuckle.AspNetCore.ReDoc.dll", + "lib/net7.0/Swashbuckle.AspNetCore.ReDoc.pdb", + "lib/net7.0/Swashbuckle.AspNetCore.ReDoc.xml", + "lib/net8.0/Swashbuckle.AspNetCore.ReDoc.dll", + "lib/net8.0/Swashbuckle.AspNetCore.ReDoc.pdb", + "lib/net8.0/Swashbuckle.AspNetCore.ReDoc.xml", + "lib/netcoreapp3.0/Swashbuckle.AspNetCore.ReDoc.dll", + "lib/netcoreapp3.0/Swashbuckle.AspNetCore.ReDoc.pdb", + "lib/netcoreapp3.0/Swashbuckle.AspNetCore.ReDoc.xml", + "lib/netstandard2.0/Swashbuckle.AspNetCore.ReDoc.dll", + "lib/netstandard2.0/Swashbuckle.AspNetCore.ReDoc.pdb", + "lib/netstandard2.0/Swashbuckle.AspNetCore.ReDoc.xml", + "package-readme.md", + "swashbuckle.aspnetcore.redoc.6.9.0.nupkg.sha512", + "swashbuckle.aspnetcore.redoc.nuspec" + ] + }, + "Swashbuckle.AspNetCore.Swagger/7.3.2": { + "sha512": "39Z2N3cLHDcDinu2IW5IaQO9wGPKTbc+vKbjIT/6gN3jFYypWY5U3O8i7Y9rmYDgxkm6niICnkcFZ3hEvikIHA==", + "type": "package", + "path": "swashbuckle.aspnetcore.swagger/7.3.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net6.0/Swashbuckle.AspNetCore.Swagger.dll", + "lib/net6.0/Swashbuckle.AspNetCore.Swagger.pdb", + "lib/net6.0/Swashbuckle.AspNetCore.Swagger.xml", + "lib/net8.0/Swashbuckle.AspNetCore.Swagger.dll", + "lib/net8.0/Swashbuckle.AspNetCore.Swagger.pdb", + "lib/net8.0/Swashbuckle.AspNetCore.Swagger.xml", + "lib/net9.0/Swashbuckle.AspNetCore.Swagger.dll", + "lib/net9.0/Swashbuckle.AspNetCore.Swagger.pdb", + "lib/net9.0/Swashbuckle.AspNetCore.Swagger.xml", + "lib/netstandard2.0/Swashbuckle.AspNetCore.Swagger.dll", + "lib/netstandard2.0/Swashbuckle.AspNetCore.Swagger.pdb", + "lib/netstandard2.0/Swashbuckle.AspNetCore.Swagger.xml", + "package-readme.md", + "swashbuckle.aspnetcore.swagger.7.3.2.nupkg.sha512", + "swashbuckle.aspnetcore.swagger.nuspec" + ] + }, + "Swashbuckle.AspNetCore.SwaggerGen/7.3.2": { + "sha512": "x5famjpcgazpT/8eEIlpSYSTZDq6ojMARhuJgj6O67Uk+Yb1DdDM5yikdL7GK1pMk5rYBETUCRWYP6rp+vHnlA==", + "type": "package", + "path": "swashbuckle.aspnetcore.swaggergen/7.3.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net6.0/Swashbuckle.AspNetCore.SwaggerGen.dll", + "lib/net6.0/Swashbuckle.AspNetCore.SwaggerGen.pdb", + "lib/net6.0/Swashbuckle.AspNetCore.SwaggerGen.xml", + "lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.dll", + "lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.pdb", + "lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.xml", + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll", + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.pdb", + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.xml", + "lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerGen.dll", + "lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerGen.pdb", + "lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerGen.xml", + "package-readme.md", + "swashbuckle.aspnetcore.swaggergen.7.3.2.nupkg.sha512", + "swashbuckle.aspnetcore.swaggergen.nuspec" + ] + }, + "Swashbuckle.AspNetCore.SwaggerUI/7.3.2": { + "sha512": "SNQuFjYin9KhkoCSVOD5/Y4VEyAjKBDeZkIqqY6x+oI21HMKzzVkeJA9V7tWFXf5df4m/+pTIlbKO7g5W9UroQ==", + "type": "package", + "path": "swashbuckle.aspnetcore.swaggerui/7.3.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net6.0/Swashbuckle.AspNetCore.SwaggerUI.dll", + "lib/net6.0/Swashbuckle.AspNetCore.SwaggerUI.pdb", + "lib/net6.0/Swashbuckle.AspNetCore.SwaggerUI.xml", + "lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.dll", + "lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.pdb", + "lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.xml", + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll", + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.pdb", + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.xml", + "lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerUI.dll", + "lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerUI.pdb", + "lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerUI.xml", + "package-readme.md", + "swashbuckle.aspnetcore.swaggerui.7.3.2.nupkg.sha512", + "swashbuckle.aspnetcore.swaggerui.nuspec" + ] + }, + "System.Drawing.Common/9.0.2": { + "sha512": "JU947wzf8JbBS16Y5EIZzAlyQU+k68D7LRx6y03s2wlhlvLqkt/8uPBrjv2hJnnaJKbdb0GhQ3JZsfYXhrRjyg==", + "type": "package", + "path": "system.drawing.common/9.0.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Drawing.Common.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/System.Drawing.Common.targets", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net462/System.Drawing.Common.dll", + "lib/net462/System.Drawing.Common.pdb", + "lib/net462/System.Drawing.Common.xml", + "lib/net8.0/System.Drawing.Common.dll", + "lib/net8.0/System.Drawing.Common.pdb", + "lib/net8.0/System.Drawing.Common.xml", + "lib/net8.0/System.Private.Windows.Core.dll", + "lib/net8.0/System.Private.Windows.Core.xml", + "lib/net9.0/System.Drawing.Common.dll", + "lib/net9.0/System.Drawing.Common.pdb", + "lib/net9.0/System.Drawing.Common.xml", + "lib/net9.0/System.Private.Windows.Core.dll", + "lib/net9.0/System.Private.Windows.Core.xml", + "lib/netstandard2.0/System.Drawing.Common.dll", + "lib/netstandard2.0/System.Drawing.Common.pdb", + "lib/netstandard2.0/System.Drawing.Common.xml", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "system.drawing.common.9.0.2.nupkg.sha512", + "system.drawing.common.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "TagLibSharp/2.3.0": { + "sha512": "Qo4z6ZjnIfbR3Us1Za5M2vQ97OWZPmODvVmepxZ8XW0UIVLGdO2T63/N3b23kCcyiwuIe0TQvMEQG8wUCCD1mA==", + "type": "package", + "path": "taglibsharp/2.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net462/TagLibSharp.dll", + "lib/net462/TagLibSharp.pdb", + "lib/net462/TaglibSharp.xml", + "lib/netstandard2.0/TagLibSharp.dll", + "lib/netstandard2.0/TagLibSharp.pdb", + "lib/netstandard2.0/TaglibSharp.xml", + "taglibsharp.2.3.0.nupkg.sha512", + "taglibsharp.nuspec" + ] + }, + "TMDbLib/2.3.0": { + "sha512": "kapjC4/ao8mxJ/G5xcZHYNVFvmAzxwWEN2PDkGbbUzAQVfbf85DsA9AUDTrXahTHYq0R7jwGyARs/y6243EPLg==", + "type": "package", + "path": "tmdblib/2.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net45/TMDbLib.dll", + "lib/net45/TMDbLib.xml", + "lib/netstandard1.2/TMDbLib.dll", + "lib/netstandard1.2/TMDbLib.xml", + "lib/netstandard2.0/TMDbLib.dll", + "lib/netstandard2.0/TMDbLib.xml", + "lib/netstandard2.1/TMDbLib.dll", + "lib/netstandard2.1/TMDbLib.xml", + "tmdblib.2.3.0.nupkg.sha512", + "tmdblib.nuspec" + ] + }, + "Ude.NetStandard/1.2.0": { + "sha512": "zRWpPAxBg3lNdm4UiKixTe+DFPoNid9CILggTCy/0WR2WKETe17kTWhiiIpLB2k5IEgnvA0QLfKlvd6Tvu0pzA==", + "type": "package", + "path": "ude.netstandard/1.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "COPYING", + "lib/net35/Ude.NetStandard.dll", + "lib/net45/Ude.NetStandard.dll", + "lib/netstandard1.0/Ude.NetStandard.dll", + "lib/netstandard1.6/Ude.NetStandard.dll", + "lib/netstandard2.0/Ude.NetStandard.dll", + "ude.netstandard.1.2.0.nupkg.sha512", + "ude.netstandard.nuspec" + ] + }, + "UTF.Unknown/2.6.0": { + "sha512": "YmmUcR9XKh3twmSjq6l0HD7qm7bj3X+gXCsdkWwpiuiB4wzGDJuCOneOQ3NvwQQEMqNeyVZOPO/EwFD4fuUoOQ==", + "type": "package", + "path": "utf.unknown/2.6.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net6.0/UtfUnknown.dll", + "lib/net6.0/UtfUnknown.xml", + "lib/net8.0/UtfUnknown.dll", + "lib/net8.0/UtfUnknown.xml", + "lib/netstandard2.0/UtfUnknown.dll", + "lib/netstandard2.0/UtfUnknown.xml", + "utf.unknown.2.6.0.nupkg.sha512", + "utf.unknown.nuspec" + ] + }, + "z440.atl.core/7.11.0": { + "sha512": "fQ4Ezr3LgTy4uRwlB4Gk6qZHlyox+RhIwCubUrq/FnLtWt406t4aSXqAN33sTxa/H0xNgZE6eL18xqpS59ErEg==", + "type": "package", + "path": "z440.atl.core/7.11.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net6.0/ATL.dll", + "lib/net6.0/ATL.xml", + "z440.atl.core.7.11.0.nupkg.sha512", + "z440.atl.core.nuspec" + ] + }, + "zlib.net-mutliplatform/1.0.8": { + "sha512": "6sYoMfSmDdfG8HgxXi0ydwCMUv5Ia1gMRh2Q0JLlTUxE9ZeWJ9dir/1Hq5MW6JcOZARRsZIOGecVx6WuAoloEA==", + "type": "package", + "path": "zlib.net-mutliplatform/1.0.8", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/netstandard1.6/zlib.net.dll", + "lib/netstandard2.0/zlib.net.dll", + "lib/netstandard2.1/zlib.net.dll", + "zlib.net-mutliplatform.1.0.8.nupkg.sha512", + "zlib.net-mutliplatform.nuspec" + ] + }, + "Emby.Photos/1.0.0": { + "type": "project", + "path": "../Emby.Photos/Emby.Photos.csproj", + "msbuildProject": "../Emby.Photos/Emby.Photos.csproj" + }, + "Jellyfin.Api/1.0.0": { + "type": "project", + "path": "../Jellyfin.Api/Jellyfin.Api.csproj", + "msbuildProject": "../Jellyfin.Api/Jellyfin.Api.csproj" + }, + "Jellyfin.CodeAnalysis/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "msbuildProject": "../src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "Jellyfin.Common/10.12.0": { + "type": "project", + "path": "../MediaBrowser.Common/MediaBrowser.Common.csproj", + "msbuildProject": "../MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "Jellyfin.Controller/10.12.0": { + "type": "project", + "path": "../MediaBrowser.Controller/MediaBrowser.Controller.csproj", + "msbuildProject": "../MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "Jellyfin.Data/10.12.0": { + "type": "project", + "path": "../Jellyfin.Data/Jellyfin.Data.csproj", + "msbuildProject": "../Jellyfin.Data/Jellyfin.Data.csproj" + }, + "Jellyfin.Database.Implementations/10.11.0": { + "type": "project", + "path": "../src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "msbuildProject": "../src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + }, + "Jellyfin.Database.Providers.Sqlite/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj", + "msbuildProject": "../src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj" + }, + "Jellyfin.Drawing/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.Drawing/Jellyfin.Drawing.csproj", + "msbuildProject": "../src/Jellyfin.Drawing/Jellyfin.Drawing.csproj" + }, + "Jellyfin.Extensions/10.12.0": { + "type": "project", + "path": "../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "msbuildProject": "../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" + }, + "Jellyfin.MediaEncoding.Hls/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj", + "msbuildProject": "../src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj" + }, + "Jellyfin.MediaEncoding.Keyframes/10.11.0": { + "type": "project", + "path": "../src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj", + "msbuildProject": "../src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj" + }, + "Jellyfin.Model/10.12.0": { + "type": "project", + "path": "../MediaBrowser.Model/MediaBrowser.Model.csproj", + "msbuildProject": "../MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "Jellyfin.Naming/10.12.0": { + "type": "project", + "path": "../Emby.Naming/Emby.Naming.csproj", + "msbuildProject": "../Emby.Naming/Emby.Naming.csproj" + }, + "Jellyfin.Networking/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.Networking/Jellyfin.Networking.csproj", + "msbuildProject": "../src/Jellyfin.Networking/Jellyfin.Networking.csproj" + }, + "Jellyfin.Server.Implementations/1.0.0": { + "type": "project", + "path": "../Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj", + "msbuildProject": "../Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj" + }, + "MediaBrowser.LocalMetadata/1.0.0": { + "type": "project", + "path": "../MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj", + "msbuildProject": "../MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj" + }, + "MediaBrowser.MediaEncoding/1.0.0": { + "type": "project", + "path": "../MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj", + "msbuildProject": "../MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj" + }, + "MediaBrowser.Providers/1.0.0": { + "type": "project", + "path": "../MediaBrowser.Providers/MediaBrowser.Providers.csproj", + "msbuildProject": "../MediaBrowser.Providers/MediaBrowser.Providers.csproj" + }, + "MediaBrowser.XbmcMetadata/1.0.0": { + "type": "project", + "path": "../MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj", + "msbuildProject": "../MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj" + } + }, + "projectFileDependencyGroups": { + "net10.0": [ + "BitFaster.Caching >= 2.5.4", + "DiscUtils.Udf >= 0.16.13", + "DotNet.Glob >= 3.1.3", + "Emby.Photos >= 1.0.0", + "Ignore >= 0.2.1", + "Jellyfin.Api >= 1.0.0", + "Jellyfin.CodeAnalysis >= 1.0.0", + "Jellyfin.Common >= 10.12.0", + "Jellyfin.Controller >= 10.12.0", + "Jellyfin.Database.Implementations >= 10.11.0", + "Jellyfin.Drawing >= 1.0.0", + "Jellyfin.Model >= 10.12.0", + "Jellyfin.Naming >= 10.12.0", + "Jellyfin.Server.Implementations >= 1.0.0", + "MediaBrowser.LocalMetadata >= 1.0.0", + "MediaBrowser.MediaEncoding >= 1.0.0", + "MediaBrowser.Providers >= 1.0.0", + "MediaBrowser.XbmcMetadata >= 1.0.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers >= 4.14.0", + "Microsoft.Data.Sqlite >= 10.0.3", + "Microsoft.EntityFrameworkCore.Relational >= 10.0.3", + "Microsoft.Extensions.Caching.Memory >= 10.0.3", + "Microsoft.Extensions.DependencyInjection >= 10.0.3", + "Microsoft.Extensions.Hosting.Abstractions >= 10.0.3", + "SerilogAnalyzer >= 0.15.0", + "SmartAnalyzers.MultithreadingAnalyzer >= 1.1.31", + "StyleCop.Analyzers >= 1.2.0-beta.556", + "prometheus-net.DotNetRuntime >= 4.4.1" + ] + }, + "packageFolders": { + "/home/wjones/.nuget/packages/": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/Emby.Server.Implementations.csproj", + "projectName": "Emby.Server.Implementations", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/Emby.Server.Implementations.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Providers/MediaBrowser.Providers.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Providers/MediaBrowser.Providers.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "BitFaster.Caching": { + "target": "Package", + "version": "[2.5.4, )", + "versionCentrallyManaged": true + }, + "DiscUtils.Udf": { + "target": "Package", + "version": "[0.16.13, )", + "versionCentrallyManaged": true + }, + "DotNet.Glob": { + "target": "Package", + "version": "[3.1.3, )", + "versionCentrallyManaged": true + }, + "Ignore": { + "target": "Package", + "version": "[0.2.1, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Data.Sqlite": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Relational": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Caching.Memory": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.DependencyInjection": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "prometheus-net.DotNetRuntime": { + "target": "Package", + "version": "[4.4.1, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + } +} \ No newline at end of file diff --git a/Emby.Server.Implementations/obj/project.nuget.cache b/Emby.Server.Implementations/obj/project.nuget.cache new file mode 100644 index 00000000..6dcc4fa6 --- /dev/null +++ b/Emby.Server.Implementations/obj/project.nuget.cache @@ -0,0 +1,86 @@ +{ + "version": 2, + "dgSpecHash": "yQkjNJYOBwE=", + "success": true, + "projectFilePath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/Emby.Server.Implementations.csproj", + "expectedPackageFiles": [ + "/home/wjones/.nuget/packages/asynckeyedlock/8.0.2/asynckeyedlock.8.0.2.nupkg.sha512", + "/home/wjones/.nuget/packages/bdinfo/0.8.0/bdinfo.0.8.0.nupkg.sha512", + "/home/wjones/.nuget/packages/bitfaster.caching/2.5.4/bitfaster.caching.2.5.4.nupkg.sha512", + "/home/wjones/.nuget/packages/diacritics/4.1.4/diacritics.4.1.4.nupkg.sha512", + "/home/wjones/.nuget/packages/discutils.core/0.16.13/discutils.core.0.16.13.nupkg.sha512", + "/home/wjones/.nuget/packages/discutils.iso9660/0.16.13/discutils.iso9660.0.16.13.nupkg.sha512", + "/home/wjones/.nuget/packages/discutils.streams/0.16.13/discutils.streams.0.16.13.nupkg.sha512", + "/home/wjones/.nuget/packages/discutils.udf/0.16.13/discutils.udf.0.16.13.nupkg.sha512", + "/home/wjones/.nuget/packages/dotnet.glob/3.1.3/dotnet.glob.3.1.3.nupkg.sha512", + "/home/wjones/.nuget/packages/icu4n/60.1.0-alpha.356/icu4n.60.1.0-alpha.356.nupkg.sha512", + "/home/wjones/.nuget/packages/icu4n.transliterator/60.1.0-alpha.356/icu4n.transliterator.60.1.0-alpha.356.nupkg.sha512", + "/home/wjones/.nuget/packages/ignore/0.2.1/ignore.0.2.1.nupkg.sha512", + "/home/wjones/.nuget/packages/j2n/2.0.0/j2n.2.0.0.nupkg.sha512", + "/home/wjones/.nuget/packages/libse/4.0.12/libse.4.0.12.nupkg.sha512", + "/home/wjones/.nuget/packages/lrcparser/2025.623.0/lrcparser.2025.623.0.nupkg.sha512", + "/home/wjones/.nuget/packages/metabrainz.common/4.1.1/metabrainz.common.4.1.1.nupkg.sha512", + "/home/wjones/.nuget/packages/metabrainz.common.json/7.2.0/metabrainz.common.json.7.2.0.nupkg.sha512", + "/home/wjones/.nuget/packages/metabrainz.musicbrainz/8.0.1/metabrainz.musicbrainz.8.0.1.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.aspnetcore.authorization/10.0.3/microsoft.aspnetcore.authorization.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.aspnetcore.metadata/10.0.3/microsoft.aspnetcore.metadata.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0/microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.data.sqlite/10.0.3/microsoft.data.sqlite.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.data.sqlite.core/10.0.3/microsoft.data.sqlite.core.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore/10.0.3/microsoft.entityframeworkcore.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.abstractions/10.0.3/microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.analyzers/10.0.3/microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.relational/10.0.3/microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.sqlite/10.0.3/microsoft.entityframeworkcore.sqlite.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.sqlite.core/10.0.3/microsoft.entityframeworkcore.sqlite.core.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.apidescription.server/6.0.5/microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.caching.abstractions/10.0.3/microsoft.extensions.caching.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.caching.memory/10.0.3/microsoft.extensions.caching.memory.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.configuration/10.0.3/microsoft.extensions.configuration.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.configuration.abstractions/10.0.3/microsoft.extensions.configuration.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.configuration.binder/10.0.3/microsoft.extensions.configuration.binder.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection/10.0.3/microsoft.extensions.dependencyinjection.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/10.0.3/microsoft.extensions.dependencyinjection.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.dependencymodel/10.0.3/microsoft.extensions.dependencymodel.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.diagnostics/10.0.3/microsoft.extensions.diagnostics.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.diagnostics.abstractions/10.0.3/microsoft.extensions.diagnostics.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.fileproviders.abstractions/10.0.3/microsoft.extensions.fileproviders.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.hosting.abstractions/10.0.3/microsoft.extensions.hosting.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.http/10.0.3/microsoft.extensions.http.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.logging/10.0.3/microsoft.extensions.logging.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.logging.abstractions/10.0.3/microsoft.extensions.logging.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.options/10.0.3/microsoft.extensions.options.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.options.configurationextensions/10.0.3/microsoft.extensions.options.configurationextensions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.primitives/10.0.3/microsoft.extensions.primitives.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.openapi/1.6.22/microsoft.openapi.1.6.22.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.win32.systemevents/9.0.2/microsoft.win32.systemevents.9.0.2.nupkg.sha512", + "/home/wjones/.nuget/packages/nebml/1.1.0.5/nebml.1.1.0.5.nupkg.sha512", + "/home/wjones/.nuget/packages/newtonsoft.json/13.0.4/newtonsoft.json.13.0.4.nupkg.sha512", + "/home/wjones/.nuget/packages/playlistsnet/1.4.1/playlistsnet.1.4.1.nupkg.sha512", + "/home/wjones/.nuget/packages/polly/8.6.5/polly.8.6.5.nupkg.sha512", + "/home/wjones/.nuget/packages/polly.core/8.6.5/polly.core.8.6.5.nupkg.sha512", + "/home/wjones/.nuget/packages/prometheus-net/3.1.2/prometheus-net.3.1.2.nupkg.sha512", + "/home/wjones/.nuget/packages/prometheus-net.dotnetruntime/4.4.1/prometheus-net.dotnetruntime.4.4.1.nupkg.sha512", + "/home/wjones/.nuget/packages/seriloganalyzer/0.15.0/seriloganalyzer.0.15.0.nupkg.sha512", + "/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31/smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512", + "/home/wjones/.nuget/packages/sqlitepclraw.bundle_e_sqlite3/2.1.11/sqlitepclraw.bundle_e_sqlite3.2.1.11.nupkg.sha512", + "/home/wjones/.nuget/packages/sqlitepclraw.core/2.1.11/sqlitepclraw.core.2.1.11.nupkg.sha512", + "/home/wjones/.nuget/packages/sqlitepclraw.lib.e_sqlite3/2.1.11/sqlitepclraw.lib.e_sqlite3.2.1.11.nupkg.sha512", + "/home/wjones/.nuget/packages/sqlitepclraw.provider.e_sqlite3/2.1.11/sqlitepclraw.provider.e_sqlite3.2.1.11.nupkg.sha512", + "/home/wjones/.nuget/packages/stylecop.analyzers/1.2.0-beta.556/stylecop.analyzers.1.2.0-beta.556.nupkg.sha512", + "/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556/stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512", + "/home/wjones/.nuget/packages/swashbuckle.aspnetcore/7.3.2/swashbuckle.aspnetcore.7.3.2.nupkg.sha512", + "/home/wjones/.nuget/packages/swashbuckle.aspnetcore.redoc/6.9.0/swashbuckle.aspnetcore.redoc.6.9.0.nupkg.sha512", + "/home/wjones/.nuget/packages/swashbuckle.aspnetcore.swagger/7.3.2/swashbuckle.aspnetcore.swagger.7.3.2.nupkg.sha512", + "/home/wjones/.nuget/packages/swashbuckle.aspnetcore.swaggergen/7.3.2/swashbuckle.aspnetcore.swaggergen.7.3.2.nupkg.sha512", + "/home/wjones/.nuget/packages/swashbuckle.aspnetcore.swaggerui/7.3.2/swashbuckle.aspnetcore.swaggerui.7.3.2.nupkg.sha512", + "/home/wjones/.nuget/packages/system.drawing.common/9.0.2/system.drawing.common.9.0.2.nupkg.sha512", + "/home/wjones/.nuget/packages/taglibsharp/2.3.0/taglibsharp.2.3.0.nupkg.sha512", + "/home/wjones/.nuget/packages/tmdblib/2.3.0/tmdblib.2.3.0.nupkg.sha512", + "/home/wjones/.nuget/packages/ude.netstandard/1.2.0/ude.netstandard.1.2.0.nupkg.sha512", + "/home/wjones/.nuget/packages/utf.unknown/2.6.0/utf.unknown.2.6.0.nupkg.sha512", + "/home/wjones/.nuget/packages/z440.atl.core/7.11.0/z440.atl.core.7.11.0.nupkg.sha512", + "/home/wjones/.nuget/packages/zlib.net-mutliplatform/1.0.8/zlib.net-mutliplatform.1.0.8.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Emby.Server.Implementations/obj/project.packagespec.json b/Emby.Server.Implementations/obj/project.packagespec.json new file mode 100644 index 00000000..ffe80c1c --- /dev/null +++ b/Emby.Server.Implementations/obj/project.packagespec.json @@ -0,0 +1 @@ +"restore":{"projectUniqueName":"/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/Emby.Server.Implementations.csproj","projectName":"Emby.Server.Implementations","projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/Emby.Server.Implementations.csproj","outputPath":"/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/obj/","projectStyle":"PackageReference","centralPackageVersionsManagementEnabled":true,"originalTargetFrameworks":["net10.0"],"sources":{"/usr/lib/dotnet/library-packs":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net10.0":{"targetAlias":"net10.0","projectReferences":{"/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Providers/MediaBrowser.Providers.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Providers/MediaBrowser.Providers.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj"}}}},"warningProperties":{"allWarningsAsErrors":true,"warnAsError":["NU1605"],"warnNotAsError":["NU1902","NU1903"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"all"},"SdkAnalysisLevel":"10.0.100"}"frameworks":{"net10.0":{"targetAlias":"net10.0","dependencies":{"BitFaster.Caching":{"target":"Package","version":"[2.5.4, )","versionCentrallyManaged":true},"DiscUtils.Udf":{"target":"Package","version":"[0.16.13, )","versionCentrallyManaged":true},"DotNet.Glob":{"target":"Package","version":"[3.1.3, )","versionCentrallyManaged":true},"Ignore":{"target":"Package","version":"[0.2.1, )","versionCentrallyManaged":true},"Microsoft.CodeAnalysis.BannedApiAnalyzers":{"include":"Runtime, Build, Native, ContentFiles, Analyzers","suppressParent":"All","target":"Package","version":"[4.14.0, )","versionCentrallyManaged":true},"Microsoft.Data.Sqlite":{"target":"Package","version":"[10.0.3, )","versionCentrallyManaged":true},"Microsoft.EntityFrameworkCore.Relational":{"target":"Package","version":"[10.0.3, )","versionCentrallyManaged":true},"Microsoft.Extensions.Caching.Memory":{"target":"Package","version":"[10.0.3, )","versionCentrallyManaged":true},"Microsoft.Extensions.DependencyInjection":{"target":"Package","version":"[10.0.3, )","versionCentrallyManaged":true},"Microsoft.Extensions.Hosting.Abstractions":{"target":"Package","version":"[10.0.3, )","versionCentrallyManaged":true},"SerilogAnalyzer":{"suppressParent":"All","target":"Package","version":"[0.15.0, )","versionCentrallyManaged":true},"SmartAnalyzers.MultithreadingAnalyzer":{"suppressParent":"All","target":"Package","version":"[1.1.31, )","versionCentrallyManaged":true},"StyleCop.Analyzers":{"suppressParent":"All","target":"Package","version":"[1.2.0-beta.556, )","versionCentrallyManaged":true},"prometheus-net.DotNetRuntime":{"target":"Package","version":"[4.4.1, )","versionCentrallyManaged":true}},"centralPackageVersions":{"AsyncKeyedLock":"8.0.2","AutoFixture":"4.18.1","AutoFixture.AutoMoq":"4.18.1","AutoFixture.Xunit2":"4.18.1","BDInfo":"0.8.0","BitFaster.Caching":"2.5.4","BlurHashSharp":"1.4.0-pre.1","BlurHashSharp.SkiaSharp":"1.4.0-pre.1","CommandLineParser":"2.9.1","coverlet.collector":"8.0.0","Diacritics":"4.1.4","DiscUtils.Udf":"0.16.13","DotNet.Glob":"3.1.3","FsCheck.Xunit":"3.3.2","HarfBuzzSharp.NativeAssets.Linux":"8.3.1.1","ICU4N.Transliterator":"60.1.0-alpha.356","IDisposableAnalyzers":"4.0.8","Ignore":"0.2.1","Jellyfin.XmlTv":"10.8.0","libse":"4.0.12","LrcParser":"2025.623.0","MetaBrainz.MusicBrainz":"8.0.1","Microsoft.AspNetCore.Authorization":"10.0.3","Microsoft.AspNetCore.Mvc.Testing":"10.0.3","Microsoft.CodeAnalysis.Analyzers":"3.11.0","Microsoft.CodeAnalysis.BannedApiAnalyzers":"4.14.0","Microsoft.CodeAnalysis.Common":"5.0.0","Microsoft.CodeAnalysis.CSharp":"5.0.0","Microsoft.Data.Sqlite":"10.0.3","Microsoft.EntityFrameworkCore.Design":"10.0.3","Microsoft.EntityFrameworkCore.Relational":"10.0.3","Microsoft.EntityFrameworkCore.Sqlite":"10.0.3","Microsoft.EntityFrameworkCore.Tools":"10.0.3","Microsoft.Extensions.Caching.Abstractions":"10.0.3","Microsoft.Extensions.Caching.Memory":"10.0.3","Microsoft.Extensions.Configuration.Abstractions":"10.0.3","Microsoft.Extensions.Configuration.Binder":"10.0.3","Microsoft.Extensions.DependencyInjection":"10.0.3","Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore":"10.0.3","Microsoft.Extensions.Hosting.Abstractions":"10.0.3","Microsoft.Extensions.Http":"10.0.3","Microsoft.Extensions.Logging":"10.0.3","Microsoft.Extensions.Options":"10.0.3","Microsoft.NET.Test.Sdk":"18.0.1","MimeTypes":"2.5.2","Moq":"4.18.4","Morestachio":"5.0.1.631","NEbml":"1.1.0.5","Newtonsoft.Json":"13.0.4","PlaylistsNET":"1.4.1","Polly":"8.6.5","prometheus-net":"8.2.1","prometheus-net.AspNetCore":"8.2.1","prometheus-net.DotNetRuntime":"4.4.1","Serilog.AspNetCore":"10.0.0","Serilog.Enrichers.Thread":"4.0.0","Serilog.Expressions":"5.0.0","Serilog.Settings.Configuration":"10.0.0","Serilog.Sinks.Async":"2.1.0","Serilog.Sinks.Console":"6.1.1","Serilog.Sinks.File":"7.0.0","Serilog.Sinks.Graylog":"3.1.1","SerilogAnalyzer":"0.15.0","SharpFuzz":"2.2.0","SkiaSharp":"[3.116.1]","SkiaSharp.HarfBuzz":"[3.116.1]","SkiaSharp.NativeAssets.Linux":"[3.116.1]","SmartAnalyzers.MultithreadingAnalyzer":"1.1.31","StyleCop.Analyzers":"1.2.0-beta.556","Svg.Skia":"3.4.1","Swashbuckle.AspNetCore":"7.3.2","Swashbuckle.AspNetCore.ReDoc":"6.9.0","System.Text.Json":"10.0.3","TagLibSharp":"2.3.0","TMDbLib":"2.3.0","UTF.Unknown":"2.6.0","xunit":"2.9.3","Xunit.Priority":"1.1.6","xunit.runner.visualstudio":"2.8.2","Xunit.SkippableFact":"1.5.61","z440.atl.core":"7.11.0"},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json","packagesToPrune":{"Microsoft.CSharp":"(,4.7.32767]","Microsoft.VisualBasic":"(,10.4.32767]","Microsoft.Win32.Primitives":"(,4.3.32767]","Microsoft.Win32.Registry":"(,5.0.32767]","runtime.any.System.Collections":"(,4.3.32767]","runtime.any.System.Diagnostics.Tools":"(,4.3.32767]","runtime.any.System.Diagnostics.Tracing":"(,4.3.32767]","runtime.any.System.Globalization":"(,4.3.32767]","runtime.any.System.Globalization.Calendars":"(,4.3.32767]","runtime.any.System.IO":"(,4.3.32767]","runtime.any.System.Reflection":"(,4.3.32767]","runtime.any.System.Reflection.Extensions":"(,4.3.32767]","runtime.any.System.Reflection.Primitives":"(,4.3.32767]","runtime.any.System.Resources.ResourceManager":"(,4.3.32767]","runtime.any.System.Runtime":"(,4.3.32767]","runtime.any.System.Runtime.Handles":"(,4.3.32767]","runtime.any.System.Runtime.InteropServices":"(,4.3.32767]","runtime.any.System.Text.Encoding":"(,4.3.32767]","runtime.any.System.Text.Encoding.Extensions":"(,4.3.32767]","runtime.any.System.Threading.Tasks":"(,4.3.32767]","runtime.any.System.Threading.Timer":"(,4.3.32767]","runtime.aot.System.Collections":"(,4.3.32767]","runtime.aot.System.Diagnostics.Tools":"(,4.3.32767]","runtime.aot.System.Diagnostics.Tracing":"(,4.3.32767]","runtime.aot.System.Globalization":"(,4.3.32767]","runtime.aot.System.Globalization.Calendars":"(,4.3.32767]","runtime.aot.System.IO":"(,4.3.32767]","runtime.aot.System.Reflection":"(,4.3.32767]","runtime.aot.System.Reflection.Extensions":"(,4.3.32767]","runtime.aot.System.Reflection.Primitives":"(,4.3.32767]","runtime.aot.System.Resources.ResourceManager":"(,4.3.32767]","runtime.aot.System.Runtime":"(,4.3.32767]","runtime.aot.System.Runtime.Handles":"(,4.3.32767]","runtime.aot.System.Runtime.InteropServices":"(,4.3.32767]","runtime.aot.System.Text.Encoding":"(,4.3.32767]","runtime.aot.System.Text.Encoding.Extensions":"(,4.3.32767]","runtime.aot.System.Threading.Tasks":"(,4.3.32767]","runtime.aot.System.Threading.Timer":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.unix.Microsoft.Win32.Primitives":"(,4.3.32767]","runtime.unix.System.Console":"(,4.3.32767]","runtime.unix.System.Diagnostics.Debug":"(,4.3.32767]","runtime.unix.System.IO.FileSystem":"(,4.3.32767]","runtime.unix.System.Net.Primitives":"(,4.3.32767]","runtime.unix.System.Net.Sockets":"(,4.3.32767]","runtime.unix.System.Private.Uri":"(,4.3.32767]","runtime.unix.System.Runtime.Extensions":"(,4.3.32767]","runtime.win.Microsoft.Win32.Primitives":"(,4.3.32767]","runtime.win.System.Console":"(,4.3.32767]","runtime.win.System.Diagnostics.Debug":"(,4.3.32767]","runtime.win.System.IO.FileSystem":"(,4.3.32767]","runtime.win.System.Net.Primitives":"(,4.3.32767]","runtime.win.System.Net.Sockets":"(,4.3.32767]","runtime.win.System.Runtime.Extensions":"(,4.3.32767]","runtime.win10-arm-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win10-arm64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win10-x64-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win10-x86-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win7-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win7-x86.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win7.System.Private.Uri":"(,4.3.32767]","runtime.win8-arm.runtime.native.System.IO.Compression":"(,4.3.32767]","System.AppContext":"(,4.3.32767]","System.Buffers":"(,5.0.32767]","System.Collections":"(,4.3.32767]","System.Collections.Concurrent":"(,4.3.32767]","System.Collections.Immutable":"(,10.0.32767]","System.Collections.NonGeneric":"(,4.3.32767]","System.Collections.Specialized":"(,4.3.32767]","System.ComponentModel":"(,4.3.32767]","System.ComponentModel.Annotations":"(,4.3.32767]","System.ComponentModel.EventBasedAsync":"(,4.3.32767]","System.ComponentModel.Primitives":"(,4.3.32767]","System.ComponentModel.TypeConverter":"(,4.3.32767]","System.Console":"(,4.3.32767]","System.Data.Common":"(,4.3.32767]","System.Data.DataSetExtensions":"(,4.4.32767]","System.Diagnostics.Contracts":"(,4.3.32767]","System.Diagnostics.Debug":"(,4.3.32767]","System.Diagnostics.DiagnosticSource":"(,10.0.32767]","System.Diagnostics.FileVersionInfo":"(,4.3.32767]","System.Diagnostics.Process":"(,4.3.32767]","System.Diagnostics.StackTrace":"(,4.3.32767]","System.Diagnostics.TextWriterTraceListener":"(,4.3.32767]","System.Diagnostics.Tools":"(,4.3.32767]","System.Diagnostics.TraceSource":"(,4.3.32767]","System.Diagnostics.Tracing":"(,4.3.32767]","System.Drawing.Primitives":"(,4.3.32767]","System.Dynamic.Runtime":"(,4.3.32767]","System.Formats.Asn1":"(,10.0.32767]","System.Formats.Tar":"(,10.0.32767]","System.Globalization":"(,4.3.32767]","System.Globalization.Calendars":"(,4.3.32767]","System.Globalization.Extensions":"(,4.3.32767]","System.IO":"(,4.3.32767]","System.IO.Compression":"(,4.3.32767]","System.IO.Compression.ZipFile":"(,4.3.32767]","System.IO.FileSystem":"(,4.3.32767]","System.IO.FileSystem.AccessControl":"(,4.4.32767]","System.IO.FileSystem.DriveInfo":"(,4.3.32767]","System.IO.FileSystem.Primitives":"(,4.3.32767]","System.IO.FileSystem.Watcher":"(,4.3.32767]","System.IO.IsolatedStorage":"(,4.3.32767]","System.IO.MemoryMappedFiles":"(,4.3.32767]","System.IO.Pipelines":"(,10.0.32767]","System.IO.Pipes":"(,4.3.32767]","System.IO.Pipes.AccessControl":"(,5.0.32767]","System.IO.UnmanagedMemoryStream":"(,4.3.32767]","System.Linq":"(,4.3.32767]","System.Linq.AsyncEnumerable":"(,10.0.32767]","System.Linq.Expressions":"(,4.3.32767]","System.Linq.Parallel":"(,4.3.32767]","System.Linq.Queryable":"(,4.3.32767]","System.Memory":"(,5.0.32767]","System.Net.Http":"(,4.3.32767]","System.Net.Http.Json":"(,10.0.32767]","System.Net.NameResolution":"(,4.3.32767]","System.Net.NetworkInformation":"(,4.3.32767]","System.Net.Ping":"(,4.3.32767]","System.Net.Primitives":"(,4.3.32767]","System.Net.Requests":"(,4.3.32767]","System.Net.Security":"(,4.3.32767]","System.Net.ServerSentEvents":"(,10.0.32767]","System.Net.Sockets":"(,4.3.32767]","System.Net.WebHeaderCollection":"(,4.3.32767]","System.Net.WebSockets":"(,4.3.32767]","System.Net.WebSockets.Client":"(,4.3.32767]","System.Numerics.Vectors":"(,5.0.32767]","System.ObjectModel":"(,4.3.32767]","System.Private.DataContractSerialization":"(,4.3.32767]","System.Private.Uri":"(,4.3.32767]","System.Reflection":"(,4.3.32767]","System.Reflection.DispatchProxy":"(,6.0.32767]","System.Reflection.Emit":"(,4.7.32767]","System.Reflection.Emit.ILGeneration":"(,4.7.32767]","System.Reflection.Emit.Lightweight":"(,4.7.32767]","System.Reflection.Extensions":"(,4.3.32767]","System.Reflection.Metadata":"(,10.0.32767]","System.Reflection.Primitives":"(,4.3.32767]","System.Reflection.TypeExtensions":"(,4.3.32767]","System.Resources.Reader":"(,4.3.32767]","System.Resources.ResourceManager":"(,4.3.32767]","System.Resources.Writer":"(,4.3.32767]","System.Runtime":"(,4.3.32767]","System.Runtime.CompilerServices.Unsafe":"(,7.0.32767]","System.Runtime.CompilerServices.VisualC":"(,4.3.32767]","System.Runtime.Extensions":"(,4.3.32767]","System.Runtime.Handles":"(,4.3.32767]","System.Runtime.InteropServices":"(,4.3.32767]","System.Runtime.InteropServices.RuntimeInformation":"(,4.3.32767]","System.Runtime.Loader":"(,4.3.32767]","System.Runtime.Numerics":"(,4.3.32767]","System.Runtime.Serialization.Formatters":"(,4.3.32767]","System.Runtime.Serialization.Json":"(,4.3.32767]","System.Runtime.Serialization.Primitives":"(,4.3.32767]","System.Runtime.Serialization.Xml":"(,4.3.32767]","System.Security.AccessControl":"(,6.0.32767]","System.Security.Claims":"(,4.3.32767]","System.Security.Cryptography.Algorithms":"(,4.3.32767]","System.Security.Cryptography.Cng":"(,5.0.32767]","System.Security.Cryptography.Csp":"(,4.3.32767]","System.Security.Cryptography.Encoding":"(,4.3.32767]","System.Security.Cryptography.OpenSsl":"(,5.0.32767]","System.Security.Cryptography.Primitives":"(,4.3.32767]","System.Security.Cryptography.X509Certificates":"(,4.3.32767]","System.Security.Principal":"(,4.3.32767]","System.Security.Principal.Windows":"(,5.0.32767]","System.Security.SecureString":"(,4.3.32767]","System.Text.Encoding":"(,4.3.32767]","System.Text.Encoding.CodePages":"(,10.0.32767]","System.Text.Encoding.Extensions":"(,4.3.32767]","System.Text.Encodings.Web":"(,10.0.32767]","System.Text.Json":"(,10.0.32767]","System.Text.RegularExpressions":"(,4.3.32767]","System.Threading":"(,4.3.32767]","System.Threading.AccessControl":"(,10.0.32767]","System.Threading.Channels":"(,10.0.32767]","System.Threading.Overlapped":"(,4.3.32767]","System.Threading.Tasks":"(,4.3.32767]","System.Threading.Tasks.Dataflow":"(,10.0.32767]","System.Threading.Tasks.Extensions":"(,5.0.32767]","System.Threading.Tasks.Parallel":"(,4.3.32767]","System.Threading.Thread":"(,4.3.32767]","System.Threading.ThreadPool":"(,4.3.32767]","System.Threading.Timer":"(,4.3.32767]","System.ValueTuple":"(,4.5.32767]","System.Xml.ReaderWriter":"(,4.3.32767]","System.Xml.XDocument":"(,4.3.32767]","System.Xml.XmlDocument":"(,4.3.32767]","System.Xml.XmlSerializer":"(,4.3.32767]","System.Xml.XPath":"(,4.3.32767]","System.Xml.XPath.XDocument":"(,5.0.32767]"}}} \ No newline at end of file diff --git a/Emby.Server.Implementations/obj/rider.project.model.nuget.info b/Emby.Server.Implementations/obj/rider.project.model.nuget.info new file mode 100644 index 00000000..74f1bac0 --- /dev/null +++ b/Emby.Server.Implementations/obj/rider.project.model.nuget.info @@ -0,0 +1 @@ +17714532054800000 \ No newline at end of file diff --git a/Emby.Server.Implementations/obj/rider.project.restore.info b/Emby.Server.Implementations/obj/rider.project.restore.info new file mode 100644 index 00000000..a06b18b2 --- /dev/null +++ b/Emby.Server.Implementations/obj/rider.project.restore.info @@ -0,0 +1 @@ +17715044202800000 \ No newline at end of file diff --git a/Jellyfin.Api/Attributes/AcceptsFileAttribute.cs b/Jellyfin.Api/Attributes/AcceptsFileAttribute.cs new file mode 100644 index 00000000..65832d26 --- /dev/null +++ b/Jellyfin.Api/Attributes/AcceptsFileAttribute.cs @@ -0,0 +1,29 @@ +#pragma warning disable CA1813 // Avoid unsealed attributes + +using System; + +namespace Jellyfin.Api.Attributes; + +/// <summary> +/// Internal produces image attribute. +/// </summary> +[AttributeUsage(AttributeTargets.Method)] +public class AcceptsFileAttribute : Attribute +{ + private readonly string[] _contentTypes; + + /// <summary> + /// Initializes a new instance of the <see cref="AcceptsFileAttribute"/> class. + /// </summary> + /// <param name="contentTypes">Content types this endpoint produces.</param> + public AcceptsFileAttribute(params string[] contentTypes) + { + _contentTypes = contentTypes; + } + + /// <summary> + /// Gets the configured content types. + /// </summary> + /// <returns>the configured content types.</returns> + public string[] ContentTypes => _contentTypes; +} diff --git a/Jellyfin.Api/Attributes/AcceptsImageFileAttribute.cs b/Jellyfin.Api/Attributes/AcceptsImageFileAttribute.cs new file mode 100644 index 00000000..26fec5fa --- /dev/null +++ b/Jellyfin.Api/Attributes/AcceptsImageFileAttribute.cs @@ -0,0 +1,17 @@ +namespace Jellyfin.Api.Attributes; + +/// <summary> +/// Produces file attribute of "image/*". +/// </summary> +public sealed class AcceptsImageFileAttribute : AcceptsFileAttribute +{ + private const string ContentType = "image/*"; + + /// <summary> + /// Initializes a new instance of the <see cref="AcceptsImageFileAttribute"/> class. + /// </summary> + public AcceptsImageFileAttribute() + : base(ContentType) + { + } +} diff --git a/Jellyfin.Api/Attributes/ParameterObsoleteAttribute.cs b/Jellyfin.Api/Attributes/ParameterObsoleteAttribute.cs new file mode 100644 index 00000000..83a29294 --- /dev/null +++ b/Jellyfin.Api/Attributes/ParameterObsoleteAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace Jellyfin.Api.Attributes; + +/// <summary> +/// Attribute to mark a parameter as obsolete. +/// </summary> +[AttributeUsage(AttributeTargets.Parameter)] +public sealed class ParameterObsoleteAttribute : Attribute +{ +} diff --git a/Jellyfin.Api/Attributes/ProducesAudioFileAttribute.cs b/Jellyfin.Api/Attributes/ProducesAudioFileAttribute.cs new file mode 100644 index 00000000..23156812 --- /dev/null +++ b/Jellyfin.Api/Attributes/ProducesAudioFileAttribute.cs @@ -0,0 +1,17 @@ +namespace Jellyfin.Api.Attributes; + +/// <summary> +/// Produces file attribute of "image/*". +/// </summary> +public sealed class ProducesAudioFileAttribute : ProducesFileAttribute +{ + private const string ContentType = "audio/*"; + + /// <summary> + /// Initializes a new instance of the <see cref="ProducesAudioFileAttribute"/> class. + /// </summary> + public ProducesAudioFileAttribute() + : base(ContentType) + { + } +} diff --git a/Jellyfin.Api/Attributes/ProducesFileAttribute.cs b/Jellyfin.Api/Attributes/ProducesFileAttribute.cs new file mode 100644 index 00000000..b4bc4506 --- /dev/null +++ b/Jellyfin.Api/Attributes/ProducesFileAttribute.cs @@ -0,0 +1,29 @@ +#pragma warning disable CA1813 // Avoid unsealed attributes + +using System; + +namespace Jellyfin.Api.Attributes; + +/// <summary> +/// Internal produces image attribute. +/// </summary> +[AttributeUsage(AttributeTargets.Method)] +public class ProducesFileAttribute : Attribute +{ + private readonly string[] _contentTypes; + + /// <summary> + /// Initializes a new instance of the <see cref="ProducesFileAttribute"/> class. + /// </summary> + /// <param name="contentTypes">Content types this endpoint produces.</param> + public ProducesFileAttribute(params string[] contentTypes) + { + _contentTypes = contentTypes; + } + + /// <summary> + /// Gets the configured content types. + /// </summary> + /// <returns>the configured content types.</returns> + public string[] ContentTypes => _contentTypes; +} diff --git a/Jellyfin.Api/Attributes/ProducesImageFileAttribute.cs b/Jellyfin.Api/Attributes/ProducesImageFileAttribute.cs new file mode 100644 index 00000000..eb1d1cae --- /dev/null +++ b/Jellyfin.Api/Attributes/ProducesImageFileAttribute.cs @@ -0,0 +1,17 @@ +namespace Jellyfin.Api.Attributes; + +/// <summary> +/// Produces file attribute of "image/*". +/// </summary> +public sealed class ProducesImageFileAttribute : ProducesFileAttribute +{ + private const string ContentType = "image/*"; + + /// <summary> + /// Initializes a new instance of the <see cref="ProducesImageFileAttribute"/> class. + /// </summary> + public ProducesImageFileAttribute() + : base(ContentType) + { + } +} diff --git a/Jellyfin.Api/Attributes/ProducesPlaylistFileAttribute.cs b/Jellyfin.Api/Attributes/ProducesPlaylistFileAttribute.cs new file mode 100644 index 00000000..73173fd3 --- /dev/null +++ b/Jellyfin.Api/Attributes/ProducesPlaylistFileAttribute.cs @@ -0,0 +1,17 @@ +namespace Jellyfin.Api.Attributes; + +/// <summary> +/// Produces file attribute of "image/*". +/// </summary> +public sealed class ProducesPlaylistFileAttribute : ProducesFileAttribute +{ + private const string ContentType = "application/x-mpegURL"; + + /// <summary> + /// Initializes a new instance of the <see cref="ProducesPlaylistFileAttribute"/> class. + /// </summary> + public ProducesPlaylistFileAttribute() + : base(ContentType) + { + } +} diff --git a/Jellyfin.Api/Attributes/ProducesVideoFileAttribute.cs b/Jellyfin.Api/Attributes/ProducesVideoFileAttribute.cs new file mode 100644 index 00000000..167da686 --- /dev/null +++ b/Jellyfin.Api/Attributes/ProducesVideoFileAttribute.cs @@ -0,0 +1,17 @@ +namespace Jellyfin.Api.Attributes; + +/// <summary> +/// Produces file attribute of "video/*". +/// </summary> +public sealed class ProducesVideoFileAttribute : ProducesFileAttribute +{ + private const string ContentType = "video/*"; + + /// <summary> + /// Initializes a new instance of the <see cref="ProducesVideoFileAttribute"/> class. + /// </summary> + public ProducesVideoFileAttribute() + : base(ContentType) + { + } +} diff --git a/Jellyfin.Api/Auth/AnonymousLanAccessPolicy/AnonymousLanAccessHandler.cs b/Jellyfin.Api/Auth/AnonymousLanAccessPolicy/AnonymousLanAccessHandler.cs new file mode 100644 index 00000000..3c1401de --- /dev/null +++ b/Jellyfin.Api/Auth/AnonymousLanAccessPolicy/AnonymousLanAccessHandler.cs @@ -0,0 +1,48 @@ +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Api.Auth.AnonymousLanAccessPolicy +{ + /// <summary> + /// LAN access handler. Allows anonymous users. + /// </summary> + public class AnonymousLanAccessHandler : AuthorizationHandler<AnonymousLanAccessRequirement> + { + private readonly INetworkManager _networkManager; + private readonly IHttpContextAccessor _httpContextAccessor; + + /// <summary> + /// Initializes a new instance of the <see cref="AnonymousLanAccessHandler"/> class. + /// </summary> + /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param> + /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param> + public AnonymousLanAccessHandler( + INetworkManager networkManager, + IHttpContextAccessor httpContextAccessor) + { + _networkManager = networkManager; + _httpContextAccessor = httpContextAccessor; + } + + /// <inheritdoc /> + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AnonymousLanAccessRequirement requirement) + { + var ip = _httpContextAccessor.HttpContext?.GetNormalizedRemoteIP(); + + // Loopback will be on LAN, so we can accept null. + if (ip is null || _networkManager.IsInLocalNetwork(ip)) + { + context.Succeed(requirement); + } + else + { + context.Fail(); + } + + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Api/Auth/AnonymousLanAccessPolicy/AnonymousLanAccessRequirement.cs b/Jellyfin.Api/Auth/AnonymousLanAccessPolicy/AnonymousLanAccessRequirement.cs new file mode 100644 index 00000000..49af24ff --- /dev/null +++ b/Jellyfin.Api/Auth/AnonymousLanAccessPolicy/AnonymousLanAccessRequirement.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.AnonymousLanAccessPolicy +{ + /// <summary> + /// The local network authorization requirement. Allows anonymous users. + /// </summary> + public class AnonymousLanAccessRequirement : IAuthorizationRequirement + { + } +} diff --git a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs new file mode 100644 index 00000000..f6f2f59c --- /dev/null +++ b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs @@ -0,0 +1,90 @@ +using System.Globalization; +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using Jellyfin.Data; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Jellyfin.Api.Auth +{ + /// <summary> + /// Custom authentication handler wrapping the legacy authentication. + /// </summary> + public class CustomAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions> + { + private readonly IAuthService _authService; + private readonly ILogger<CustomAuthenticationHandler> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="CustomAuthenticationHandler" /> class. + /// </summary> + /// <param name="authService">The jellyfin authentication service.</param> + /// <param name="options">Options monitor.</param> + /// <param name="logger">The logger.</param> + /// <param name="encoder">The url encoder.</param> + public CustomAuthenticationHandler( + IAuthService authService, + IOptionsMonitor<AuthenticationSchemeOptions> options, + ILoggerFactory logger, + UrlEncoder encoder) + : base(options, logger, encoder) + { + _authService = authService; + _logger = logger.CreateLogger<CustomAuthenticationHandler>(); + } + + /// <inheritdoc /> + protected override async Task<AuthenticateResult> HandleAuthenticateAsync() + { + try + { + var authorizationInfo = await _authService.Authenticate(Request).ConfigureAwait(false); + if (!authorizationInfo.HasToken) + { + return AuthenticateResult.NoResult(); + } + + var role = UserRoles.User; + if (authorizationInfo.IsApiKey + || (authorizationInfo.User?.HasPermission(PermissionKind.IsAdministrator) ?? false)) + { + role = UserRoles.Administrator; + } + + var claims = new[] + { + new Claim(ClaimTypes.Name, authorizationInfo.User?.Username ?? string.Empty), + new Claim(ClaimTypes.Role, role), + new Claim(InternalClaimTypes.UserId, authorizationInfo.UserId.ToString("N", CultureInfo.InvariantCulture)), + new Claim(InternalClaimTypes.DeviceId, authorizationInfo.DeviceId ?? string.Empty), + new Claim(InternalClaimTypes.Device, authorizationInfo.Device ?? string.Empty), + new Claim(InternalClaimTypes.Client, authorizationInfo.Client ?? string.Empty), + new Claim(InternalClaimTypes.Version, authorizationInfo.Version ?? string.Empty), + new Claim(InternalClaimTypes.Token, authorizationInfo.Token), + new Claim(InternalClaimTypes.IsApiKey, authorizationInfo.IsApiKey.ToString(CultureInfo.InvariantCulture)) + }; + + var identity = new ClaimsIdentity(claims, Scheme.Name); + var principal = new ClaimsPrincipal(identity); + var ticket = new AuthenticationTicket(principal, Scheme.Name); + + return AuthenticateResult.Success(ticket); + } + catch (AuthenticationException ex) + { + _logger.LogDebug(ex, "Error authenticating with {Handler}", nameof(CustomAuthenticationHandler)); + return AuthenticateResult.NoResult(); + } + catch (SecurityException ex) + { + return AuthenticateResult.Fail(ex); + } + } + } +} diff --git a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs new file mode 100644 index 00000000..6b80d537 --- /dev/null +++ b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs @@ -0,0 +1,96 @@ +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using Jellyfin.Api.Extensions; +using Jellyfin.Data; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Library; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy +{ + /// <summary> + /// Default authorization handler. + /// </summary> + public class DefaultAuthorizationHandler : AuthorizationHandler<DefaultAuthorizationRequirement> + { + private readonly IUserManager _userManager; + private readonly INetworkManager _networkManager; + private readonly IHttpContextAccessor _httpContextAccessor; + + /// <summary> + /// Initializes a new instance of the <see cref="DefaultAuthorizationHandler"/> class. + /// </summary> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param> + /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param> + public DefaultAuthorizationHandler( + IUserManager userManager, + INetworkManager networkManager, + IHttpContextAccessor httpContextAccessor) + { + _userManager = userManager; + _networkManager = networkManager; + _httpContextAccessor = httpContextAccessor; + } + + /// <inheritdoc /> + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DefaultAuthorizationRequirement requirement) + { + var isApiKey = context.User.GetIsApiKey(); + var userId = context.User.GetUserId(); + // This likely only happens during the wizard, so skip the default checks and let any other handlers do it + if (!isApiKey && userId.IsEmpty()) + { + return Task.CompletedTask; + } + + if (isApiKey) + { + // Api keys are unrestricted. + context.Succeed(requirement); + return Task.CompletedTask; + } + + var isInLocalNetwork = _httpContextAccessor.HttpContext is not null + && _networkManager.IsInLocalNetwork(_httpContextAccessor.HttpContext.GetNormalizedRemoteIP()); + var user = _userManager.GetUserById(userId); + if (user is null) + { + throw new ResourceNotFoundException(); + } + + // User cannot access remotely and user is remote + if (!isInLocalNetwork && !user.HasPermission(PermissionKind.EnableRemoteAccess)) + { + context.Fail(); + return Task.CompletedTask; + } + + // Admins can do everything + if (context.User.IsInRole(UserRoles.Administrator)) + { + context.Succeed(requirement); + return Task.CompletedTask; + } + + // It's not great to have this check, but parental schedule must usually be honored except in a few rare cases + if (requirement.ValidateParentalSchedule && !user.IsParentalScheduleAllowed()) + { + context.Fail(); + return Task.CompletedTask; + } + + // Only succeed if the requirement isn't a subclass as any subclassed requirement will handle success in its own handler + if (requirement.GetType() == typeof(DefaultAuthorizationRequirement)) + { + context.Succeed(requirement); + } + + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationRequirement.cs b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationRequirement.cs new file mode 100644 index 00000000..22238475 --- /dev/null +++ b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationRequirement.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy +{ + /// <summary> + /// The default authorization requirement. + /// </summary> + public class DefaultAuthorizationRequirement : IAuthorizationRequirement + { + /// <summary> + /// Initializes a new instance of the <see cref="DefaultAuthorizationRequirement"/> class. + /// </summary> + /// <param name="validateParentalSchedule">A value indicating whether to validate parental schedule.</param> + public DefaultAuthorizationRequirement(bool validateParentalSchedule = true) + { + ValidateParentalSchedule = validateParentalSchedule; + } + + /// <summary> + /// Gets a value indicating whether to ignore parental schedule. + /// </summary> + public bool ValidateParentalSchedule { get; } + } +} diff --git a/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs b/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs new file mode 100644 index 00000000..e425000c --- /dev/null +++ b/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs @@ -0,0 +1,56 @@ +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using Jellyfin.Api.Extensions; +using MediaBrowser.Common.Configuration; +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.FirstTimeSetupPolicy +{ + /// <summary> + /// Authorization handler for requiring first time setup or default privileges. + /// </summary> + public class FirstTimeSetupHandler : AuthorizationHandler<FirstTimeSetupRequirement> + { + private readonly IConfigurationManager _configurationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="FirstTimeSetupHandler" /> class. + /// </summary> + /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param> + public FirstTimeSetupHandler(IConfigurationManager configurationManager) + { + _configurationManager = configurationManager; + } + + /// <inheritdoc /> + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FirstTimeSetupRequirement requirement) + { + // Succeed if the startup wizard / first time setup is not complete + if (!_configurationManager.CommonConfiguration.IsStartupWizardCompleted) + { + context.Succeed(requirement); + } + + // Succeed if user is admin + else if (context.User.IsInRole(UserRoles.Administrator)) + { + context.Succeed(requirement); + } + + // Fail if admin is required and user is not admin + else if (requirement.RequireAdmin) + { + context.Fail(); + } + + // Succeed if admin is not required and user is not guest + else if (context.User.IsInRole(UserRoles.User)) + { + context.Succeed(requirement); + } + + // Any user-specific checks are handled in the DefaultAuthorizationHandler. + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupRequirement.cs b/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupRequirement.cs new file mode 100644 index 00000000..adae12ef --- /dev/null +++ b/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupRequirement.cs @@ -0,0 +1,25 @@ +using Jellyfin.Api.Auth.DefaultAuthorizationPolicy; + +namespace Jellyfin.Api.Auth.FirstTimeSetupPolicy +{ + /// <summary> + /// The authorization requirement, requiring incomplete first time setup or default privileges, for the authorization handler. + /// </summary> + public class FirstTimeSetupRequirement : DefaultAuthorizationRequirement + { + /// <summary> + /// Initializes a new instance of the <see cref="FirstTimeSetupRequirement"/> class. + /// </summary> + /// <param name="validateParentalSchedule">A value indicating whether to ignore parental schedule.</param> + /// <param name="requireAdmin">A value indicating whether administrator role is required.</param> + public FirstTimeSetupRequirement(bool validateParentalSchedule = false, bool requireAdmin = true) : base(validateParentalSchedule) + { + RequireAdmin = requireAdmin; + } + + /// <summary> + /// Gets a value indicating whether administrator role is required. + /// </summary> + public bool RequireAdmin { get; } + } +} diff --git a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs new file mode 100644 index 00000000..557b7d3a --- /dev/null +++ b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs @@ -0,0 +1,56 @@ +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy +{ + /// <summary> + /// Local access or require elevated privileges handler. + /// </summary> + public class LocalAccessOrRequiresElevationHandler : AuthorizationHandler<LocalAccessOrRequiresElevationRequirement> + { + private readonly INetworkManager _networkManager; + private readonly IHttpContextAccessor _httpContextAccessor; + + /// <summary> + /// Initializes a new instance of the <see cref="LocalAccessOrRequiresElevationHandler"/> class. + /// </summary> + /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param> + /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param> + public LocalAccessOrRequiresElevationHandler( + INetworkManager networkManager, + IHttpContextAccessor httpContextAccessor) + { + _networkManager = networkManager; + _httpContextAccessor = httpContextAccessor; + } + + /// <inheritdoc /> + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LocalAccessOrRequiresElevationRequirement requirement) + { + var ip = _httpContextAccessor.HttpContext?.GetNormalizedRemoteIP(); + + // Loopback will be on LAN, so we can accept null. + if (ip is null || _networkManager.IsInLocalNetwork(ip)) + { + context.Succeed(requirement); + + return Task.CompletedTask; + } + + if (context.User.IsInRole(UserRoles.Administrator)) + { + context.Succeed(requirement); + } + else + { + context.Fail(); + } + + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs new file mode 100644 index 00000000..f633c69d --- /dev/null +++ b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy +{ + /// <summary> + /// The local access or elevated privileges authorization requirement. + /// </summary> + public class LocalAccessOrRequiresElevationRequirement : IAuthorizationRequirement + { + } +} diff --git a/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs b/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs new file mode 100644 index 00000000..7efb5b16 --- /dev/null +++ b/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs @@ -0,0 +1,77 @@ +using System.Threading.Tasks; +using Jellyfin.Api.Extensions; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.SyncPlay; +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy +{ + /// <summary> + /// Default authorization handler. + /// </summary> + public class SyncPlayAccessHandler : AuthorizationHandler<SyncPlayAccessRequirement> + { + private readonly ISyncPlayManager _syncPlayManager; + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="SyncPlayAccessHandler"/> class. + /// </summary> + /// <param name="syncPlayManager">Instance of the <see cref="ISyncPlayManager"/> interface.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + public SyncPlayAccessHandler( + ISyncPlayManager syncPlayManager, + IUserManager userManager) + { + _syncPlayManager = syncPlayManager; + _userManager = userManager; + } + + /// <inheritdoc /> + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, SyncPlayAccessRequirement requirement) + { + var userId = context.User.GetUserId(); + var user = _userManager.GetUserById(userId); + if (user is null) + { + throw new ResourceNotFoundException(); + } + + if (requirement.RequiredAccess == SyncPlayAccessRequirementType.HasAccess) + { + if (user.SyncPlayAccess is SyncPlayUserAccessType.CreateAndJoinGroups or SyncPlayUserAccessType.JoinGroups + || _syncPlayManager.IsUserActive(userId)) + { + context.Succeed(requirement); + } + } + else if (requirement.RequiredAccess == SyncPlayAccessRequirementType.CreateGroup) + { + if (user.SyncPlayAccess == SyncPlayUserAccessType.CreateAndJoinGroups) + { + context.Succeed(requirement); + } + } + else if (requirement.RequiredAccess == SyncPlayAccessRequirementType.JoinGroup) + { + if (user.SyncPlayAccess == SyncPlayUserAccessType.CreateAndJoinGroups + || user.SyncPlayAccess == SyncPlayUserAccessType.JoinGroups) + { + context.Succeed(requirement); + } + } + else if (requirement.RequiredAccess == SyncPlayAccessRequirementType.IsInGroup) + { + if (_syncPlayManager.IsUserActive(userId)) + { + context.Succeed(requirement); + } + } + + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessRequirement.cs b/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessRequirement.cs new file mode 100644 index 00000000..bb6e52b4 --- /dev/null +++ b/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessRequirement.cs @@ -0,0 +1,25 @@ +using Jellyfin.Api.Auth.DefaultAuthorizationPolicy; +using Jellyfin.Data.Enums; + +namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy +{ + /// <summary> + /// The default authorization requirement. + /// </summary> + public class SyncPlayAccessRequirement : DefaultAuthorizationRequirement + { + /// <summary> + /// Initializes a new instance of the <see cref="SyncPlayAccessRequirement"/> class. + /// </summary> + /// <param name="requiredAccess">A value of <see cref="SyncPlayAccessRequirementType"/>.</param> + public SyncPlayAccessRequirement(SyncPlayAccessRequirementType requiredAccess) + { + RequiredAccess = requiredAccess; + } + + /// <summary> + /// Gets the required SyncPlay access. + /// </summary> + public SyncPlayAccessRequirementType RequiredAccess { get; } + } +} diff --git a/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionHandler.cs b/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionHandler.cs new file mode 100644 index 00000000..d139eab1 --- /dev/null +++ b/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionHandler.cs @@ -0,0 +1,56 @@ +using System.Threading.Tasks; +using Jellyfin.Api.Extensions; +using Jellyfin.Data; +using Jellyfin.Extensions; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Library; +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.UserPermissionPolicy +{ + /// <summary> + /// User permission authorization handler. + /// </summary> + public class UserPermissionHandler : AuthorizationHandler<UserPermissionRequirement> + { + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="UserPermissionHandler"/> class. + /// </summary> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + public UserPermissionHandler(IUserManager userManager) + { + _userManager = userManager; + } + + /// <inheritdoc /> + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UserPermissionRequirement requirement) + { + // Api keys have global permissions, so just succeed the requirement. + if (context.User.GetIsApiKey()) + { + context.Succeed(requirement); + } + else + { + var userId = context.User.GetUserId(); + if (!userId.IsEmpty()) + { + var user = _userManager.GetUserById(context.User.GetUserId()); + if (user is null) + { + throw new ResourceNotFoundException(); + } + + if (user.HasPermission(requirement.RequiredPermission)) + { + context.Succeed(requirement); + } + } + } + + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionRequirement.cs b/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionRequirement.cs new file mode 100644 index 00000000..152c400c --- /dev/null +++ b/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionRequirement.cs @@ -0,0 +1,26 @@ +using Jellyfin.Api.Auth.DefaultAuthorizationPolicy; +using Jellyfin.Database.Implementations.Enums; + +namespace Jellyfin.Api.Auth.UserPermissionPolicy +{ + /// <summary> + /// The user permission requirement. + /// </summary> + public class UserPermissionRequirement : DefaultAuthorizationRequirement + { + /// <summary> + /// Initializes a new instance of the <see cref="UserPermissionRequirement"/> class. + /// </summary> + /// <param name="requiredPermission">The required <see cref="PermissionKind"/>.</param> + /// <param name="validateParentalSchedule">Whether to validate the user's parental schedule.</param> + public UserPermissionRequirement(PermissionKind requiredPermission, bool validateParentalSchedule = true) : base(validateParentalSchedule) + { + RequiredPermission = requiredPermission; + } + + /// <summary> + /// Gets the required user permission. + /// </summary> + public PermissionKind RequiredPermission { get; } + } +} diff --git a/Jellyfin.Api/BaseJellyfinApiController.cs b/Jellyfin.Api/BaseJellyfinApiController.cs new file mode 100644 index 00000000..5b4bd0ad --- /dev/null +++ b/Jellyfin.Api/BaseJellyfinApiController.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Net.Mime; +using Jellyfin.Api.Results; +using Jellyfin.Extensions.Json; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api; + +/// <summary> +/// Base api controller for the API setting a default route. +/// </summary> +[ApiController] +[Route("[controller]")] +[Produces( + MediaTypeNames.Application.Json, + JsonDefaults.CamelCaseMediaType, + JsonDefaults.PascalCaseMediaType)] +public class BaseJellyfinApiController : ControllerBase +{ + /// <summary> + /// Create a new <see cref="OkResult{T}"/>. + /// </summary> + /// <param name="value">The value to return.</param> + /// <typeparam name="T">The type to return.</typeparam> + /// <returns>The <see cref="ActionResult{T}"/>.</returns> + protected ActionResult<IEnumerable<T>> Ok<T>(IEnumerable<T>? value) + => new OkResult<IEnumerable<T>?>(value); + + /// <summary> + /// Create a new <see cref="OkResult{T}"/>. + /// </summary> + /// <param name="value">The value to return.</param> + /// <typeparam name="T">The type to return.</typeparam> + /// <returns>The <see cref="ActionResult{T}"/>.</returns> + protected ActionResult<T> Ok<T>(T value) + => new OkResult<T>(value); +} diff --git a/Jellyfin.Api/Constants/AuthenticationSchemes.cs b/Jellyfin.Api/Constants/AuthenticationSchemes.cs new file mode 100644 index 00000000..d5c2253e --- /dev/null +++ b/Jellyfin.Api/Constants/AuthenticationSchemes.cs @@ -0,0 +1,12 @@ +namespace Jellyfin.Api.Constants; + +/// <summary> +/// Authentication schemes for user authentication in the API. +/// </summary> +public static class AuthenticationSchemes +{ + /// <summary> + /// Scheme name for the custom legacy authentication. + /// </summary> + public const string CustomAuthentication = "CustomAuthentication"; +} diff --git a/Jellyfin.Api/Constants/InternalClaimTypes.cs b/Jellyfin.Api/Constants/InternalClaimTypes.cs new file mode 100644 index 00000000..28195575 --- /dev/null +++ b/Jellyfin.Api/Constants/InternalClaimTypes.cs @@ -0,0 +1,42 @@ +namespace Jellyfin.Api.Constants; + +/// <summary> +/// Internal claim types for authorization. +/// </summary> +public static class InternalClaimTypes +{ + /// <summary> + /// User Id. + /// </summary> + public const string UserId = "Jellyfin-UserId"; + + /// <summary> + /// Device Id. + /// </summary> + public const string DeviceId = "Jellyfin-DeviceId"; + + /// <summary> + /// Device. + /// </summary> + public const string Device = "Jellyfin-Device"; + + /// <summary> + /// Client. + /// </summary> + public const string Client = "Jellyfin-Client"; + + /// <summary> + /// Version. + /// </summary> + public const string Version = "Jellyfin-Version"; + + /// <summary> + /// Token. + /// </summary> + public const string Token = "Jellyfin-Token"; + + /// <summary> + /// Is Api Key. + /// </summary> + public const string IsApiKey = "Jellyfin-IsApiKey"; +} diff --git a/Jellyfin.Api/Constants/UserRoles.cs b/Jellyfin.Api/Constants/UserRoles.cs new file mode 100644 index 00000000..41c7b7cd --- /dev/null +++ b/Jellyfin.Api/Constants/UserRoles.cs @@ -0,0 +1,22 @@ +namespace Jellyfin.Api.Constants; + +/// <summary> +/// Constants for user roles used in the authentication and authorization for the API. +/// </summary> +public static class UserRoles +{ + /// <summary> + /// Guest user. + /// </summary> + public const string Guest = "Guest"; + + /// <summary> + /// Regular user with no special privileges. + /// </summary> + public const string User = "User"; + + /// <summary> + /// Administrator user with elevated privileges. + /// </summary> + public const string Administrator = "Administrator"; +} diff --git a/Jellyfin.Api/Controllers/ActivityLogController.cs b/Jellyfin.Api/Controllers/ActivityLogController.cs new file mode 100644 index 00000000..47d3f4b7 --- /dev/null +++ b/Jellyfin.Api/Controllers/ActivityLogController.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using Jellyfin.Data.Queries; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Common.Api; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Activity log controller. +/// </summary> +[Route("System/ActivityLog")] +[Authorize(Policy = Policies.RequiresElevation)] +public class ActivityLogController : BaseJellyfinApiController +{ + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="ActivityLogController"/> class. + /// </summary> + /// <param name="activityManager">Instance of <see cref="IActivityManager"/> interface.</param> + public ActivityLogController(IActivityManager activityManager) + { + _activityManager = activityManager; + } + + /// <summary> + /// Gets activity log entries. + /// </summary> + /// <param name="startIndex">The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">The maximum number of records to return.</param> + /// <param name="minDate">The minimum date.</param> + /// <param name="maxDate">The maximum date.</param> + /// <param name="hasUserId">Filter log entries if it has user id, or not.</param> + /// <param name="name">Filter by name.</param> + /// <param name="overview">Filter by overview.</param> + /// <param name="shortOverview">Filter by short overview.</param> + /// <param name="type">Filter by type.</param> + /// <param name="itemId">Filter by item id.</param> + /// <param name="username">Filter by username.</param> + /// <param name="severity">Filter by log severity.</param> + /// <param name="sortBy">Specify one or more sort orders. Format: SortBy=Name,Type.</param> + /// <param name="sortOrder">Sort Order..</param> + /// <response code="200">Activity log returned.</response> + /// <returns>A <see cref="QueryResult{ActivityLogEntry}"/> containing the log entries.</returns> + [HttpGet("Entries")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<QueryResult<ActivityLogEntry>>> GetLogEntries( + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] DateTime? minDate, + [FromQuery] DateTime? maxDate, + [FromQuery] bool? hasUserId, + [FromQuery] string? name, + [FromQuery] string? overview, + [FromQuery] string? shortOverview, + [FromQuery] string? type, + [FromQuery] Guid? itemId, + [FromQuery] string? username, + [FromQuery] LogLevel? severity, + [FromQuery] ActivityLogSortBy[]? sortBy, + [FromQuery] SortOrder[]? sortOrder) + { + var query = new ActivityLogQuery + { + Skip = startIndex, + Limit = limit, + MinDate = minDate, + MaxDate = maxDate, + HasUserId = hasUserId, + Name = name, + Overview = overview, + ShortOverview = shortOverview, + Type = type, + ItemId = itemId, + Username = username, + Severity = severity, + OrderBy = GetOrderBy(sortBy ?? [], sortOrder ?? []), + }; + + return await _activityManager.GetPagedResultAsync(query).ConfigureAwait(false); + } + + private static (ActivityLogSortBy SortBy, SortOrder SortOrder)[] GetOrderBy( + IReadOnlyList<ActivityLogSortBy> sortBy, + IReadOnlyList<SortOrder> requestedSortOrder) + { + if (sortBy.Count == 0) + { + return []; + } + + var result = new (ActivityLogSortBy, SortOrder)[sortBy.Count]; + var i = 0; + for (; i < requestedSortOrder.Count; i++) + { + result[i] = (sortBy[i], requestedSortOrder[i]); + } + + // Add remaining elements with the first specified SortOrder + // or the default one if no SortOrders are specified + var order = requestedSortOrder.Count > 0 ? requestedSortOrder[0] : SortOrder.Ascending; + for (; i < sortBy.Count; i++) + { + result[i] = (sortBy[i], order); + } + + return result; + } +} diff --git a/Jellyfin.Api/Controllers/ApiKeyController.cs b/Jellyfin.Api/Controllers/ApiKeyController.cs new file mode 100644 index 00000000..3363d7ba --- /dev/null +++ b/Jellyfin.Api/Controllers/ApiKeyController.cs @@ -0,0 +1,76 @@ +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using MediaBrowser.Common.Api; +using MediaBrowser.Controller.Security; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Authentication controller. +/// </summary> +[Route("Auth")] +public class ApiKeyController : BaseJellyfinApiController +{ + private readonly IAuthenticationManager _authenticationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="ApiKeyController"/> class. + /// </summary> + /// <param name="authenticationManager">Instance of <see cref="IAuthenticationManager"/> interface.</param> + public ApiKeyController(IAuthenticationManager authenticationManager) + { + _authenticationManager = authenticationManager; + } + + /// <summary> + /// Get all keys. + /// </summary> + /// <response code="200">Api keys retrieved.</response> + /// <returns>A <see cref="QueryResult{AuthenticationInfo}"/> with all keys.</returns> + [HttpGet("Keys")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<QueryResult<AuthenticationInfo>>> GetKeys() + { + var keys = await _authenticationManager.GetApiKeys().ConfigureAwait(false); + + return new QueryResult<AuthenticationInfo>(keys); + } + + /// <summary> + /// Create a new api key. + /// </summary> + /// <param name="app">Name of the app using the authentication key.</param> + /// <response code="204">Api key created.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Keys")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> CreateKey([FromQuery, Required] string app) + { + await _authenticationManager.CreateApiKey(app).ConfigureAwait(false); + + return NoContent(); + } + + /// <summary> + /// Remove an api key. + /// </summary> + /// <param name="key">The access token to delete.</param> + /// <response code="204">Api key deleted.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpDelete("Keys/{key}")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> RevokeKey([FromRoute, Required] string key) + { + await _authenticationManager.DeleteApiKey(key).ConfigureAwait(false); + + return NoContent(); + } +} diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs new file mode 100644 index 00000000..642790f9 --- /dev/null +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -0,0 +1,481 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The artists controller. +/// </summary> +[Route("Artists")] +[Authorize] +public class ArtistsController : BaseJellyfinApiController +{ + private readonly ILibraryManager _libraryManager; + private readonly IUserManager _userManager; + private readonly IDtoService _dtoService; + + /// <summary> + /// Initializes a new instance of the <see cref="ArtistsController"/> class. + /// </summary> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> + public ArtistsController( + ILibraryManager libraryManager, + IUserManager userManager, + IDtoService dtoService) + { + _libraryManager = libraryManager; + _userManager = userManager; + _dtoService = dtoService; + } + + /// <summary> + /// Gets all artists from a given item, folder, or the entire library. + /// </summary> + /// <param name="minCommunityRating">Optional filter by minimum community rating.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="searchTerm">Optional. Search term.</param> + /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param> + /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param> + /// <param name="filters">Optional. Specify additional filters to apply.</param> + /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param> + /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param> + /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param> + /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.</param> + /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.</param> + /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.</param> + /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.</param> + /// <param name="enableUserData">Optional, include user data.</param> + /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="person">Optional. If specified, results will be filtered to include only those containing the specified person.</param> + /// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the specified person ids.</param> + /// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.</param> + /// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.</param> + /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.</param> + /// <param name="userId">User id.</param> + /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param> + /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param> + /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param> + /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited.</param> + /// <param name="sortOrder">Sort Order - Ascending,Descending.</param> + /// <param name="enableImages">Optional, include image information in output.</param> + /// <param name="enableTotalRecordCount">Total record count.</param> + /// <response code="200">Artists returned.</response> + /// <returns>An <see cref="OkResult"/> containing the artists.</returns> + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetArtists( + [FromQuery] double? minCommunityRating, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] string? searchTerm, + [FromQuery] Guid? parentId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters, + [FromQuery] bool? isFavorite, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery] string? person, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds, + [FromQuery] Guid? userId, + [FromQuery] string? nameStartsWithOrGreater, + [FromQuery] string? nameStartsWith, + [FromQuery] string? nameLessThan, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, + [FromQuery] bool? enableImages = true, + [FromQuery] bool enableTotalRecordCount = true) + { + userId = RequestHelpers.GetUserId(User, userId); + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + + User? user = null; + BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId); + + if (!userId.IsNullOrEmpty()) + { + user = _userManager.GetUserById(userId.Value); + } + + var query = new InternalItemsQuery(user) + { + ExcludeItemTypes = excludeItemTypes, + IncludeItemTypes = includeItemTypes, + MediaTypes = mediaTypes, + StartIndex = startIndex, + Limit = limit, + IsFavorite = isFavorite, + NameLessThan = nameLessThan, + NameStartsWith = nameStartsWith, + NameStartsWithOrGreater = nameStartsWithOrGreater, + Tags = tags, + OfficialRatings = officialRatings, + Genres = genres, + GenreIds = genreIds, + StudioIds = studioIds, + Person = person, + PersonIds = personIds, + PersonTypes = personTypes, + Years = years, + MinCommunityRating = minCommunityRating, + DtoOptions = dtoOptions, + SearchTerm = searchTerm, + EnableTotalRecordCount = enableTotalRecordCount, + OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder) + }; + + if (parentId.HasValue) + { + if (parentItem is Folder) + { + query.AncestorIds = new[] { parentId.Value }; + } + else + { + query.ItemIds = new[] { parentId.Value }; + } + } + + // Studios + if (studios.Length != 0) + { + query.StudioIds = studios.Select(i => + { + try + { + return _libraryManager.GetStudio(i); + } + catch + { + return null; + } + }).Where(i => i is not null).Select(i => i!.Id).ToArray(); + } + + foreach (var filter in filters) + { + switch (filter) + { + case ItemFilter.Dislikes: + query.IsLiked = false; + break; + case ItemFilter.IsFavorite: + query.IsFavorite = true; + break; + case ItemFilter.IsFavoriteOrLikes: + query.IsFavoriteOrLiked = true; + break; + case ItemFilter.IsFolder: + query.IsFolder = true; + break; + case ItemFilter.IsNotFolder: + query.IsFolder = false; + break; + case ItemFilter.IsPlayed: + query.IsPlayed = true; + break; + case ItemFilter.IsResumable: + query.IsResumable = true; + break; + case ItemFilter.IsUnplayed: + query.IsPlayed = false; + break; + case ItemFilter.Likes: + query.IsLiked = true; + break; + } + } + + var result = _libraryManager.GetArtists(query); + + var dtos = result.Items.Select(i => + { + var (baseItem, itemCounts) = i; + var dto = _dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user); + + if (includeItemTypes.Length != 0) + { + dto.ChildCount = itemCounts.ItemCount; + dto.ProgramCount = itemCounts.ProgramCount; + dto.SeriesCount = itemCounts.SeriesCount; + dto.EpisodeCount = itemCounts.EpisodeCount; + dto.MovieCount = itemCounts.MovieCount; + dto.TrailerCount = itemCounts.TrailerCount; + dto.AlbumCount = itemCounts.AlbumCount; + dto.SongCount = itemCounts.SongCount; + dto.ArtistCount = itemCounts.ArtistCount; + } + + return dto; + }); + + return new QueryResult<BaseItemDto>( + query.StartIndex, + result.TotalRecordCount, + dtos.ToArray()); + } + + /// <summary> + /// Gets all album artists from a given item, folder, or the entire library. + /// </summary> + /// <param name="minCommunityRating">Optional filter by minimum community rating.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="searchTerm">Optional. Search term.</param> + /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param> + /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param> + /// <param name="filters">Optional. Specify additional filters to apply.</param> + /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param> + /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param> + /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param> + /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.</param> + /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.</param> + /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.</param> + /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.</param> + /// <param name="enableUserData">Optional, include user data.</param> + /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="person">Optional. If specified, results will be filtered to include only those containing the specified person.</param> + /// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the specified person ids.</param> + /// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.</param> + /// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.</param> + /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.</param> + /// <param name="userId">User id.</param> + /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param> + /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param> + /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param> + /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited.</param> + /// <param name="sortOrder">Sort Order - Ascending,Descending.</param> + /// <param name="enableImages">Optional, include image information in output.</param> + /// <param name="enableTotalRecordCount">Total record count.</param> + /// <response code="200">Album artists returned.</response> + /// <returns>An <see cref="OkResult"/> containing the album artists.</returns> + [HttpGet("AlbumArtists")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetAlbumArtists( + [FromQuery] double? minCommunityRating, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] string? searchTerm, + [FromQuery] Guid? parentId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters, + [FromQuery] bool? isFavorite, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery] string? person, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds, + [FromQuery] Guid? userId, + [FromQuery] string? nameStartsWithOrGreater, + [FromQuery] string? nameStartsWith, + [FromQuery] string? nameLessThan, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, + [FromQuery] bool? enableImages = true, + [FromQuery] bool enableTotalRecordCount = true) + { + userId = RequestHelpers.GetUserId(User, userId); + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + + User? user = null; + BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId); + + if (!userId.IsNullOrEmpty()) + { + user = _userManager.GetUserById(userId.Value); + } + + var query = new InternalItemsQuery(user) + { + ExcludeItemTypes = excludeItemTypes, + IncludeItemTypes = includeItemTypes, + MediaTypes = mediaTypes, + StartIndex = startIndex, + Limit = limit, + IsFavorite = isFavorite, + NameLessThan = nameLessThan, + NameStartsWith = nameStartsWith, + NameStartsWithOrGreater = nameStartsWithOrGreater, + Tags = tags, + OfficialRatings = officialRatings, + Genres = genres, + GenreIds = genreIds, + StudioIds = studioIds, + Person = person, + PersonIds = personIds, + PersonTypes = personTypes, + Years = years, + MinCommunityRating = minCommunityRating, + DtoOptions = dtoOptions, + SearchTerm = searchTerm, + EnableTotalRecordCount = enableTotalRecordCount, + OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder) + }; + + if (parentId.HasValue) + { + if (parentItem is Folder) + { + query.AncestorIds = new[] { parentId.Value }; + } + else + { + query.ItemIds = new[] { parentId.Value }; + } + } + + // Studios + if (studios.Length != 0) + { + query.StudioIds = studios.Select(i => + { + try + { + return _libraryManager.GetStudio(i); + } + catch + { + return null; + } + }).Where(i => i is not null).Select(i => i!.Id).ToArray(); + } + + foreach (var filter in filters) + { + switch (filter) + { + case ItemFilter.Dislikes: + query.IsLiked = false; + break; + case ItemFilter.IsFavorite: + query.IsFavorite = true; + break; + case ItemFilter.IsFavoriteOrLikes: + query.IsFavoriteOrLiked = true; + break; + case ItemFilter.IsFolder: + query.IsFolder = true; + break; + case ItemFilter.IsNotFolder: + query.IsFolder = false; + break; + case ItemFilter.IsPlayed: + query.IsPlayed = true; + break; + case ItemFilter.IsResumable: + query.IsResumable = true; + break; + case ItemFilter.IsUnplayed: + query.IsPlayed = false; + break; + case ItemFilter.Likes: + query.IsLiked = true; + break; + } + } + + var result = _libraryManager.GetAlbumArtists(query); + + var dtos = result.Items.Select(i => + { + var (baseItem, itemCounts) = i; + var dto = _dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user); + + if (includeItemTypes.Length != 0) + { + dto.ChildCount = itemCounts.ItemCount; + dto.ProgramCount = itemCounts.ProgramCount; + dto.SeriesCount = itemCounts.SeriesCount; + dto.EpisodeCount = itemCounts.EpisodeCount; + dto.MovieCount = itemCounts.MovieCount; + dto.TrailerCount = itemCounts.TrailerCount; + dto.AlbumCount = itemCounts.AlbumCount; + dto.SongCount = itemCounts.SongCount; + dto.ArtistCount = itemCounts.ArtistCount; + } + + return dto; + }); + + return new QueryResult<BaseItemDto>( + query.StartIndex, + result.TotalRecordCount, + dtos.ToArray()); + } + + /// <summary> + /// Gets an artist by name. + /// </summary> + /// <param name="name">Studio name.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <response code="200">Artist returned.</response> + /// <returns>An <see cref="OkResult"/> containing the artist.</returns> + [HttpGet("{name}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<BaseItemDto> GetArtistByName([FromRoute, Required] string name, [FromQuery] Guid? userId) + { + userId = RequestHelpers.GetUserId(User, userId); + var dtoOptions = new DtoOptions(); + + var item = _libraryManager.GetArtist(name, dtoOptions); + + if (!userId.IsNullOrEmpty()) + { + var user = _userManager.GetUserById(userId.Value); + + return _dtoService.GetBaseItemDto(item, dtoOptions, user); + } + + return _dtoService.GetBaseItemDto(item, dtoOptions); + } +} diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs new file mode 100644 index 00000000..4be79ff5 --- /dev/null +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -0,0 +1,361 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Jellyfin.Api.Attributes; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.Models.StreamingDtos; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; +using MediaBrowser.Model.Dlna; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The audio controller. +/// </summary> +public class AudioController : BaseJellyfinApiController +{ + private readonly AudioHelper _audioHelper; + + private readonly TranscodingJobType _transcodingJobType = TranscodingJobType.Progressive; + + /// <summary> + /// Initializes a new instance of the <see cref="AudioController"/> class. + /// </summary> + /// <param name="audioHelper">Instance of <see cref="AudioHelper"/>.</param> + public AudioController(AudioHelper audioHelper) + { + _audioHelper = audioHelper; + } + + /// <summary> + /// Gets an audio stream. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="container">The audio container.</param> + /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param> + /// <param name="params">The streaming parameters.</param> + /// <param name="tag">The tag.</param> + /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param> + /// <param name="playSessionId">The play session id.</param> + /// <param name="segmentContainer">The segment container.</param> + /// <param name="segmentLength">The segment length.</param> + /// <param name="minSegments">The minimum number of segments.</param> + /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> + /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension.</param> + /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> + /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> + /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> + /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param> + /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param> + /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param> + /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param> + /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param> + /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param> + /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param> + /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param> + /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param> + /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param> + /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param> + /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param> + /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param> + /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param> + /// <param name="maxRefFrames">Optional.</param> + /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param> + /// <param name="requireAvc">Optional. Whether to require avc.</param> + /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param> + /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param> + /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param> + /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> + /// <param name="liveStreamId">The live stream id.</param> + /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension.</param> + /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> + /// <param name="transcodeReasons">Optional. The transcoding reason.</param> + /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> + /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param> + /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param> + /// <param name="streamOptions">Optional. The streaming options.</param> + /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param> + /// <response code="200">Audio stream returned.</response> + /// <returns>A <see cref="FileResult"/> containing the audio file.</returns> + [HttpGet("{itemId}/stream", Name = "GetAudioStream")] + [HttpHead("{itemId}/stream", Name = "HeadAudioStream")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesAudioFile] + public async Task<ActionResult> GetAudioStream( + [FromRoute, Required] Guid itemId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? container, + [FromQuery] bool? @static, + [FromQuery] string? @params, + [FromQuery] string? tag, + [FromQuery, ParameterObsolete] string? deviceProfileId, + [FromQuery] string? playSessionId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer, + [FromQuery] int? segmentLength, + [FromQuery] int? minSegments, + [FromQuery] string? mediaSourceId, + [FromQuery] string? deviceId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec, + [FromQuery] bool? enableAutoStreamCopy, + [FromQuery] bool? allowVideoStreamCopy, + [FromQuery] bool? allowAudioStreamCopy, + [FromQuery] int? audioSampleRate, + [FromQuery] int? maxAudioBitDepth, + [FromQuery] int? audioBitRate, + [FromQuery] int? audioChannels, + [FromQuery] int? maxAudioChannels, + [FromQuery] string? profile, + [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level, + [FromQuery] float? framerate, + [FromQuery] float? maxFramerate, + [FromQuery] bool? copyTimestamps, + [FromQuery] long? startTimeTicks, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? videoBitRate, + [FromQuery] int? subtitleStreamIndex, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, + [FromQuery] int? maxRefFrames, + [FromQuery] int? maxVideoBitDepth, + [FromQuery] bool? requireAvc, + [FromQuery] bool? deInterlace, + [FromQuery] bool? requireNonAnamorphic, + [FromQuery] int? transcodingMaxAudioChannels, + [FromQuery] int? cpuCoreLimit, + [FromQuery] string? liveStreamId, + [FromQuery] bool? enableMpegtsM2TsMode, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec, + [FromQuery] string? transcodeReasons, + [FromQuery] int? audioStreamIndex, + [FromQuery] int? videoStreamIndex, + [FromQuery] EncodingContext? context, + [FromQuery] Dictionary<string, string>? streamOptions, + [FromQuery] bool enableAudioVbrEncoding = true) + { + StreamingRequestDto streamingRequest = new StreamingRequestDto + { + Id = itemId, + Container = container, + Static = @static ?? false, + Params = @params, + Tag = tag, + PlaySessionId = playSessionId, + SegmentContainer = segmentContainer, + SegmentLength = segmentLength, + MinSegments = minSegments, + MediaSourceId = mediaSourceId, + DeviceId = deviceId, + AudioCodec = audioCodec, + EnableAutoStreamCopy = enableAutoStreamCopy ?? true, + AllowAudioStreamCopy = allowAudioStreamCopy ?? true, + AllowVideoStreamCopy = allowVideoStreamCopy ?? true, + AudioSampleRate = audioSampleRate, + MaxAudioChannels = maxAudioChannels, + AudioBitRate = audioBitRate, + MaxAudioBitDepth = maxAudioBitDepth, + AudioChannels = audioChannels, + Profile = profile, + Level = level, + Framerate = framerate, + MaxFramerate = maxFramerate, + CopyTimestamps = copyTimestamps ?? false, + StartTimeTicks = startTimeTicks, + Width = width, + Height = height, + VideoBitRate = videoBitRate, + SubtitleStreamIndex = subtitleStreamIndex, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, + MaxRefFrames = maxRefFrames, + MaxVideoBitDepth = maxVideoBitDepth, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, + TranscodingMaxAudioChannels = transcodingMaxAudioChannels, + CpuCoreLimit = cpuCoreLimit, + LiveStreamId = liveStreamId, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, + VideoCodec = videoCodec, + SubtitleCodec = subtitleCodec, + TranscodeReasons = transcodeReasons, + AudioStreamIndex = audioStreamIndex, + VideoStreamIndex = videoStreamIndex, + Context = context ?? EncodingContext.Static, + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding + }; + + return await _audioHelper.GetAudioStream(_transcodingJobType, streamingRequest).ConfigureAwait(false); + } + + /// <summary> + /// Gets an audio stream. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="container">The audio container.</param> + /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param> + /// <param name="params">The streaming parameters.</param> + /// <param name="tag">The tag.</param> + /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param> + /// <param name="playSessionId">The play session id.</param> + /// <param name="segmentContainer">The segment container.</param> + /// <param name="segmentLength">The segment length.</param> + /// <param name="minSegments">The minimum number of segments.</param> + /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> + /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension.</param> + /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> + /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> + /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> + /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param> + /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param> + /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param> + /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param> + /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param> + /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param> + /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param> + /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param> + /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param> + /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param> + /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param> + /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param> + /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param> + /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param> + /// <param name="maxRefFrames">Optional.</param> + /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param> + /// <param name="requireAvc">Optional. Whether to require avc.</param> + /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param> + /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param> + /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param> + /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> + /// <param name="liveStreamId">The live stream id.</param> + /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension.</param> + /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> + /// <param name="transcodeReasons">Optional. The transcoding reason.</param> + /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> + /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param> + /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param> + /// <param name="streamOptions">Optional. The streaming options.</param> + /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param> + /// <response code="200">Audio stream returned.</response> + /// <returns>A <see cref="FileResult"/> containing the audio file.</returns> + [HttpGet("{itemId}/stream.{container}", Name = "GetAudioStreamByContainer")] + [HttpHead("{itemId}/stream.{container}", Name = "HeadAudioStreamByContainer")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesAudioFile] + public async Task<ActionResult> GetAudioStreamByContainer( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container, + [FromQuery] bool? @static, + [FromQuery] string? @params, + [FromQuery] string? tag, + [FromQuery, ParameterObsolete] string? deviceProfileId, + [FromQuery] string? playSessionId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer, + [FromQuery] int? segmentLength, + [FromQuery] int? minSegments, + [FromQuery] string? mediaSourceId, + [FromQuery] string? deviceId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec, + [FromQuery] bool? enableAutoStreamCopy, + [FromQuery] bool? allowVideoStreamCopy, + [FromQuery] bool? allowAudioStreamCopy, + [FromQuery] int? audioSampleRate, + [FromQuery] int? maxAudioBitDepth, + [FromQuery] int? audioBitRate, + [FromQuery] int? audioChannels, + [FromQuery] int? maxAudioChannels, + [FromQuery] string? profile, + [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level, + [FromQuery] float? framerate, + [FromQuery] float? maxFramerate, + [FromQuery] bool? copyTimestamps, + [FromQuery] long? startTimeTicks, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? videoBitRate, + [FromQuery] int? subtitleStreamIndex, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, + [FromQuery] int? maxRefFrames, + [FromQuery] int? maxVideoBitDepth, + [FromQuery] bool? requireAvc, + [FromQuery] bool? deInterlace, + [FromQuery] bool? requireNonAnamorphic, + [FromQuery] int? transcodingMaxAudioChannels, + [FromQuery] int? cpuCoreLimit, + [FromQuery] string? liveStreamId, + [FromQuery] bool? enableMpegtsM2TsMode, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec, + [FromQuery] string? transcodeReasons, + [FromQuery] int? audioStreamIndex, + [FromQuery] int? videoStreamIndex, + [FromQuery] EncodingContext? context, + [FromQuery] Dictionary<string, string>? streamOptions, + [FromQuery] bool enableAudioVbrEncoding = true) + { + StreamingRequestDto streamingRequest = new StreamingRequestDto + { + Id = itemId, + Container = container, + Static = @static ?? false, + Params = @params, + Tag = tag, + PlaySessionId = playSessionId, + SegmentContainer = segmentContainer, + SegmentLength = segmentLength, + MinSegments = minSegments, + MediaSourceId = mediaSourceId, + DeviceId = deviceId, + AudioCodec = audioCodec, + EnableAutoStreamCopy = enableAutoStreamCopy ?? true, + AllowAudioStreamCopy = allowAudioStreamCopy ?? true, + AllowVideoStreamCopy = allowVideoStreamCopy ?? true, + AudioSampleRate = audioSampleRate, + MaxAudioChannels = maxAudioChannels, + AudioBitRate = audioBitRate, + MaxAudioBitDepth = maxAudioBitDepth, + AudioChannels = audioChannels, + Profile = profile, + Level = level, + Framerate = framerate, + MaxFramerate = maxFramerate, + CopyTimestamps = copyTimestamps ?? false, + StartTimeTicks = startTimeTicks, + Width = width, + Height = height, + VideoBitRate = videoBitRate, + SubtitleStreamIndex = subtitleStreamIndex, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, + MaxRefFrames = maxRefFrames, + MaxVideoBitDepth = maxVideoBitDepth, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, + TranscodingMaxAudioChannels = transcodingMaxAudioChannels, + CpuCoreLimit = cpuCoreLimit, + LiveStreamId = liveStreamId, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, + VideoCodec = videoCodec, + SubtitleCodec = subtitleCodec, + TranscodeReasons = transcodeReasons, + AudioStreamIndex = audioStreamIndex, + VideoStreamIndex = videoStreamIndex, + Context = context ?? EncodingContext.Static, + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding + }; + + return await _audioHelper.GetAudioStream(_transcodingJobType, streamingRequest).ConfigureAwait(false); + } +} diff --git a/Jellyfin.Api/Controllers/BackupController.cs b/Jellyfin.Api/Controllers/BackupController.cs new file mode 100644 index 00000000..aa908ee3 --- /dev/null +++ b/Jellyfin.Api/Controllers/BackupController.cs @@ -0,0 +1,127 @@ +using System.IO; +using System.Threading.Tasks; +using Jellyfin.Server.Implementations.SystemBackupService; +using MediaBrowser.Common.Api; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.SystemBackupService; +using Microsoft.AspNetCore.Authentication.OAuth.Claims; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The backup controller. +/// </summary> +[Authorize(Policy = Policies.RequiresElevation)] +public class BackupController : BaseJellyfinApiController +{ + private readonly IBackupService _backupService; + private readonly IApplicationPaths _applicationPaths; + + /// <summary> + /// Initializes a new instance of the <see cref="BackupController"/> class. + /// </summary> + /// <param name="backupService">Instance of the <see cref="IBackupService"/> interface.</param> + /// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param> + public BackupController(IBackupService backupService, IApplicationPaths applicationPaths) + { + _backupService = backupService; + _applicationPaths = applicationPaths; + } + + /// <summary> + /// Creates a new Backup. + /// </summary> + /// <param name="backupOptions">The backup options.</param> + /// <response code="200">Backup created.</response> + /// <response code="403">User does not have permission to retrieve information.</response> + /// <returns>The created backup manifest.</returns> + [HttpPost("Create")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task<ActionResult<BackupManifestDto>> CreateBackup([FromBody] BackupOptionsDto backupOptions) + { + return Ok(await _backupService.CreateBackupAsync(backupOptions ?? new()).ConfigureAwait(false)); + } + + /// <summary> + /// Restores to a backup by restarting the server and applying the backup. + /// </summary> + /// <param name="archiveRestoreDto">The data to start a restore process.</param> + /// <response code="204">Backup restore started.</response> + /// <response code="403">User does not have permission to retrieve information.</response> + /// <returns>No-Content.</returns> + [HttpPost("Restore")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public IActionResult StartRestoreBackup([FromBody, BindRequired] BackupRestoreRequestDto archiveRestoreDto) + { + var archivePath = SanitizePath(archiveRestoreDto.ArchiveFileName); + if (!System.IO.File.Exists(archivePath)) + { + return NotFound(); + } + + _backupService.ScheduleRestoreAndRestartServer(archivePath); + return NoContent(); + } + + /// <summary> + /// Gets a list of all currently present backups in the backup directory. + /// </summary> + /// <response code="200">Backups available.</response> + /// <response code="403">User does not have permission to retrieve information.</response> + /// <returns>The list of backups.</returns> + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task<ActionResult<BackupManifestDto[]>> ListBackups() + { + return Ok(await _backupService.EnumerateBackups().ConfigureAwait(false)); + } + + /// <summary> + /// Gets the descriptor from an existing archive is present. + /// </summary> + /// <param name="path">The data to start a restore process.</param> + /// <response code="200">Backup archive manifest.</response> + /// <response code="204">Not a valid jellyfin Archive.</response> + /// <response code="404">Not a valid path.</response> + /// <response code="403">User does not have permission to retrieve information.</response> + /// <returns>The backup manifest.</returns> + [HttpGet("Manifest")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task<ActionResult<BackupManifestDto>> GetBackup([BindRequired] string path) + { + var backupPath = SanitizePath(path); + + if (!System.IO.File.Exists(backupPath)) + { + return NotFound(); + } + + var manifest = await _backupService.GetBackupManifest(backupPath).ConfigureAwait(false); + if (manifest is null) + { + return NoContent(); + } + + return Ok(manifest); + } + + [NonAction] + private string SanitizePath(string path) + { + // sanitize path + var archiveRestorePath = Path.GetFileName(Path.GetFullPath(path)); + var archivePath = Path.Combine(_applicationPaths.BackupPath, archiveRestorePath); + return archivePath; + } +} diff --git a/Jellyfin.Api/Controllers/BrandingController.cs b/Jellyfin.Api/Controllers/BrandingController.cs new file mode 100644 index 00000000..1d948ff2 --- /dev/null +++ b/Jellyfin.Api/Controllers/BrandingController.cs @@ -0,0 +1,65 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Branding; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Branding controller. +/// </summary> +public class BrandingController : BaseJellyfinApiController +{ + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="BrandingController"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + public BrandingController(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// <summary> + /// Gets branding configuration. + /// </summary> + /// <response code="200">Branding configuration returned.</response> + /// <returns>An <see cref="OkResult"/> containing the branding configuration.</returns> + [HttpGet("Configuration")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<BrandingOptionsDto> GetBrandingOptions() + { + var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding"); + + var brandingOptionsDto = new BrandingOptionsDto + { + LoginDisclaimer = brandingOptions.LoginDisclaimer, + CustomCss = brandingOptions.CustomCss, + SplashscreenEnabled = brandingOptions.SplashscreenEnabled + }; + + return brandingOptionsDto; + } + + /// <summary> + /// Gets branding css. + /// </summary> + /// <response code="200">Branding css returned.</response> + /// <response code="204">No branding css configured.</response> + /// <returns> + /// An <see cref="OkResult"/> containing the branding css if exist, + /// or a <see cref="NoContentResult"/> if the css is not configured. + /// </returns> + [HttpGet("Css")] + [HttpGet("Css.css", Name = "GetBrandingCss_2")] + [Produces("text/css")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult<string> GetBrandingCss() + { + var options = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding"); + return options.CustomCss ?? string.Empty; + } +} diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs new file mode 100644 index 00000000..880b3a82 --- /dev/null +++ b/Jellyfin.Api/Controllers/ChannelsController.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Channels; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Channels Controller. +/// </summary> +[Authorize] +public class ChannelsController : BaseJellyfinApiController +{ + private readonly IChannelManager _channelManager; + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="ChannelsController"/> class. + /// </summary> + /// <param name="channelManager">Instance of the <see cref="IChannelManager"/> interface.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + public ChannelsController(IChannelManager channelManager, IUserManager userManager) + { + _channelManager = channelManager; + _userManager = userManager; + } + + /// <summary> + /// Gets available channels. + /// </summary> + /// <param name="userId">User Id to filter by. Use <see cref="Guid.Empty"/> to not filter by user.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="supportsLatestItems">Optional. Filter by channels that support getting latest items.</param> + /// <param name="supportsMediaDeletion">Optional. Filter by channels that support media deletion.</param> + /// <param name="isFavorite">Optional. Filter by channels that are favorite.</param> + /// <response code="200">Channels returned.</response> + /// <returns>An <see cref="OkResult"/> containing the channels.</returns> + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<QueryResult<BaseItemDto>>> GetChannels( + [FromQuery] Guid? userId, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] bool? supportsLatestItems, + [FromQuery] bool? supportsMediaDeletion, + [FromQuery] bool? isFavorite) + { + userId = RequestHelpers.GetUserId(User, userId); + return await _channelManager.GetChannelsAsync(new ChannelQuery + { + Limit = limit, + StartIndex = startIndex, + UserId = userId.Value, + SupportsLatestItems = supportsLatestItems, + SupportsMediaDeletion = supportsMediaDeletion, + IsFavorite = isFavorite + }).ConfigureAwait(false); + } + + /// <summary> + /// Get all channel features. + /// </summary> + /// <response code="200">All channel features returned.</response> + /// <returns>An <see cref="OkResult"/> containing the channel features.</returns> + [HttpGet("Features")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<IEnumerable<ChannelFeatures>> GetAllChannelFeatures() + { + return _channelManager.GetAllChannelFeatures(); + } + + /// <summary> + /// Get channel features. + /// </summary> + /// <param name="channelId">Channel id.</param> + /// <response code="200">Channel features returned.</response> + /// <returns>An <see cref="OkResult"/> containing the channel features.</returns> + [HttpGet("{channelId}/Features")] + public ActionResult<ChannelFeatures> GetChannelFeatures([FromRoute, Required] Guid channelId) + { + return _channelManager.GetChannelFeatures(channelId); + } + + /// <summary> + /// Get channel items. + /// </summary> + /// <param name="channelId">Channel Id.</param> + /// <param name="folderId">Optional. Folder Id.</param> + /// <param name="userId">Optional. User Id.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="sortOrder">Optional. Sort Order - Ascending,Descending.</param> + /// <param name="filters">Optional. Specify additional filters to apply.</param> + /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <response code="200">Channel items returned.</response> + /// <returns> + /// A <see cref="Task"/> representing the request to get the channel items. + /// The task result contains an <see cref="OkResult"/> containing the channel items. + /// </returns> + [HttpGet("{channelId}/Items")] + public async Task<ActionResult<QueryResult<BaseItemDto>>> GetChannelItems( + [FromRoute, Required] Guid channelId, + [FromQuery] Guid? folderId, + [FromQuery] Guid? userId, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + + var query = new InternalItemsQuery(user) + { + Limit = limit, + StartIndex = startIndex, + ChannelIds = new[] { channelId }, + ParentId = folderId ?? Guid.Empty, + OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder), + DtoOptions = new DtoOptions { Fields = fields } + }; + + foreach (var filter in filters) + { + switch (filter) + { + case ItemFilter.IsFolder: + query.IsFolder = true; + break; + case ItemFilter.IsNotFolder: + query.IsFolder = false; + break; + case ItemFilter.IsUnplayed: + query.IsPlayed = false; + break; + case ItemFilter.IsPlayed: + query.IsPlayed = true; + break; + case ItemFilter.IsFavorite: + query.IsFavorite = true; + break; + case ItemFilter.IsResumable: + query.IsResumable = true; + break; + case ItemFilter.Likes: + query.IsLiked = true; + break; + case ItemFilter.Dislikes: + query.IsLiked = false; + break; + case ItemFilter.IsFavoriteOrLikes: + query.IsFavoriteOrLiked = true; + break; + } + } + + return await _channelManager.GetChannelItems(query, CancellationToken.None).ConfigureAwait(false); + } + + /// <summary> + /// Gets latest channel items. + /// </summary> + /// <param name="userId">Optional. User Id.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="filters">Optional. Specify additional filters to apply.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="channelIds">Optional. Specify one or more channel id's, comma delimited.</param> + /// <response code="200">Latest channel items returned.</response> + /// <returns> + /// A <see cref="Task"/> representing the request to get the latest channel items. + /// The task result contains an <see cref="OkResult"/> containing the latest channel items. + /// </returns> + [HttpGet("Items/Latest")] + public async Task<ActionResult<QueryResult<BaseItemDto>>> GetLatestChannelItems( + [FromQuery] Guid? userId, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] channelIds) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + + var query = new InternalItemsQuery(user) + { + Limit = limit, + StartIndex = startIndex, + ChannelIds = channelIds, + DtoOptions = new DtoOptions { Fields = fields } + }; + + foreach (var filter in filters) + { + switch (filter) + { + case ItemFilter.IsFolder: + query.IsFolder = true; + break; + case ItemFilter.IsNotFolder: + query.IsFolder = false; + break; + case ItemFilter.IsUnplayed: + query.IsPlayed = false; + break; + case ItemFilter.IsPlayed: + query.IsPlayed = true; + break; + case ItemFilter.IsFavorite: + query.IsFavorite = true; + break; + case ItemFilter.IsResumable: + query.IsResumable = true; + break; + case ItemFilter.Likes: + query.IsLiked = true; + break; + case ItemFilter.Dislikes: + query.IsLiked = false; + break; + case ItemFilter.IsFavoriteOrLikes: + query.IsFavoriteOrLiked = true; + break; + } + } + + return await _channelManager.GetLatestChannelItems(query, CancellationToken.None).ConfigureAwait(false); + } +} diff --git a/Jellyfin.Api/Controllers/ClientLogController.cs b/Jellyfin.Api/Controllers/ClientLogController.cs new file mode 100644 index 00000000..139888bd --- /dev/null +++ b/Jellyfin.Api/Controllers/ClientLogController.cs @@ -0,0 +1,78 @@ +using System.Net.Mime; +using System.Threading.Tasks; +using Jellyfin.Api.Attributes; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Models.ClientLogDtos; +using MediaBrowser.Controller.ClientEvent; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Client log controller. +/// </summary> +[Authorize] +public class ClientLogController : BaseJellyfinApiController +{ + private const int MaxDocumentSize = 1_000_000; + private readonly IClientEventLogger _clientEventLogger; + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="ClientLogController"/> class. + /// </summary> + /// <param name="clientEventLogger">Instance of the <see cref="IClientEventLogger"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + public ClientLogController( + IClientEventLogger clientEventLogger, + IServerConfigurationManager serverConfigurationManager) + { + _clientEventLogger = clientEventLogger; + _serverConfigurationManager = serverConfigurationManager; + } + + /// <summary> + /// Upload a document. + /// </summary> + /// <response code="200">Document saved.</response> + /// <response code="403">Event logging disabled.</response> + /// <response code="413">Upload size too large.</response> + /// <returns>Create response.</returns> + [HttpPost("Document")] + [ProducesResponseType(typeof(ClientLogDocumentResponseDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status413PayloadTooLarge)] + [AcceptsFile(MediaTypeNames.Text.Plain)] + [RequestSizeLimit(MaxDocumentSize)] + public async Task<ActionResult<ClientLogDocumentResponseDto>> LogFile() + { + if (!_serverConfigurationManager.Configuration.AllowClientLogUpload) + { + return Forbid(); + } + + if (Request.ContentLength > MaxDocumentSize) + { + // Manually validate to return proper status code. + return StatusCode(StatusCodes.Status413PayloadTooLarge, $"Payload must be less than {MaxDocumentSize:N0} bytes"); + } + + var (clientName, clientVersion) = GetRequestInformation(); + var fileName = await _clientEventLogger.WriteDocumentAsync(clientName, clientVersion, Request.Body) + .ConfigureAwait(false); + return Ok(new ClientLogDocumentResponseDto(fileName)); + } + + private (string ClientName, string ClientVersion) GetRequestInformation() + { + var clientName = HttpContext.User.GetClient() ?? "unknown-client"; + var clientVersion = HttpContext.User.GetIsApiKey() + ? "apikey" + : HttpContext.User.GetVersion() ?? "unknown-version"; + + return (clientName, clientVersion); + } +} diff --git a/Jellyfin.Api/Controllers/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs new file mode 100644 index 00000000..227487b3 --- /dev/null +++ b/Jellyfin.Api/Controllers/CollectionController.cs @@ -0,0 +1,111 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.ModelBinders; +using MediaBrowser.Common.Api; +using MediaBrowser.Controller.Collections; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Model.Collections; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The collection controller. +/// </summary> +[Route("Collections")] +[Authorize(Policy = Policies.CollectionManagement)] +public class CollectionController : BaseJellyfinApiController +{ + private readonly ICollectionManager _collectionManager; + private readonly IDtoService _dtoService; + + /// <summary> + /// Initializes a new instance of the <see cref="CollectionController"/> class. + /// </summary> + /// <param name="collectionManager">Instance of <see cref="ICollectionManager"/> interface.</param> + /// <param name="dtoService">Instance of <see cref="IDtoService"/> interface.</param> + public CollectionController( + ICollectionManager collectionManager, + IDtoService dtoService) + { + _collectionManager = collectionManager; + _dtoService = dtoService; + } + + /// <summary> + /// Creates a new collection. + /// </summary> + /// <param name="name">The name of the collection.</param> + /// <param name="ids">Item Ids to add to the collection.</param> + /// <param name="parentId">Optional. Create the collection within a specific folder.</param> + /// <param name="isLocked">Whether or not to lock the new collection.</param> + /// <response code="200">Collection created.</response> + /// <returns>A <see cref="CollectionCreationOptions"/> with information about the new collection.</returns> + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<CollectionCreationResult>> CreateCollection( + [FromQuery] string? name, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] ids, + [FromQuery] Guid? parentId, + [FromQuery] bool isLocked = false) + { + var userId = User.GetUserId(); + + var item = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions + { + IsLocked = isLocked, + Name = name, + ParentId = parentId, + ItemIdList = ids, + UserIds = new[] { userId } + }).ConfigureAwait(false); + + var dtoOptions = new DtoOptions(); + + var dto = _dtoService.GetBaseItemDto(item, dtoOptions); + + return new CollectionCreationResult + { + Id = dto.Id + }; + } + + /// <summary> + /// Adds items to a collection. + /// </summary> + /// <param name="collectionId">The collection id.</param> + /// <param name="ids">Item ids, comma delimited.</param> + /// <response code="204">Items added to collection.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("{collectionId}/Items")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> AddToCollection( + [FromRoute, Required] Guid collectionId, + [FromQuery, Required, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids) + { + await _collectionManager.AddToCollectionAsync(collectionId, ids).ConfigureAwait(true); + return NoContent(); + } + + /// <summary> + /// Removes items from a collection. + /// </summary> + /// <param name="collectionId">The collection id.</param> + /// <param name="ids">Item ids, comma delimited.</param> + /// <response code="204">Items removed from collection.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpDelete("{collectionId}/Items")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> RemoveFromCollection( + [FromRoute, Required] Guid collectionId, + [FromQuery, Required, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids) + { + await _collectionManager.RemoveFromCollectionAsync(collectionId, ids).ConfigureAwait(false); + return NoContent(); + } +} diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs new file mode 100644 index 00000000..9e03fbeb --- /dev/null +++ b/Jellyfin.Api/Controllers/ConfigurationController.cs @@ -0,0 +1,144 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Net.Mime; +using System.Text.Json; +using Jellyfin.Api.Attributes; +using Jellyfin.Extensions.Json; +using MediaBrowser.Common.Api; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Branding; +using MediaBrowser.Model.Configuration; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Configuration Controller. +/// </summary> +[Route("System")] +[Authorize] +public class ConfigurationController : BaseJellyfinApiController +{ + private readonly IServerConfigurationManager _configurationManager; + private readonly IMediaEncoder _mediaEncoder; + + private readonly JsonSerializerOptions _serializerOptions = JsonDefaults.Options; + + /// <summary> + /// Initializes a new instance of the <see cref="ConfigurationController"/> class. + /// </summary> + /// <param name="configurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> + public ConfigurationController( + IServerConfigurationManager configurationManager, + IMediaEncoder mediaEncoder) + { + _configurationManager = configurationManager; + _mediaEncoder = mediaEncoder; + } + + /// <summary> + /// Gets application configuration. + /// </summary> + /// <response code="200">Application configuration returned.</response> + /// <returns>Application configuration.</returns> + [HttpGet("Configuration")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<ServerConfiguration> GetConfiguration() + { + return _configurationManager.Configuration; + } + + /// <summary> + /// Updates application configuration. + /// </summary> + /// <param name="configuration">Configuration.</param> + /// <response code="204">Configuration updated.</response> + /// <returns>Update status.</returns> + [HttpPost("Configuration")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult UpdateConfiguration([FromBody, Required] ServerConfiguration configuration) + { + _configurationManager.ReplaceConfiguration(configuration); + return NoContent(); + } + + /// <summary> + /// Gets a named configuration. + /// </summary> + /// <param name="key">Configuration key.</param> + /// <response code="200">Configuration returned.</response> + /// <returns>Configuration.</returns> + [HttpGet("Configuration/{key}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesFile(MediaTypeNames.Application.Json)] + public ActionResult<object> GetNamedConfiguration([FromRoute, Required] string key) + { + return _configurationManager.GetConfiguration(key); + } + + /// <summary> + /// Updates named configuration. + /// </summary> + /// <param name="key">Configuration key.</param> + /// <param name="configuration">Configuration.</param> + /// <response code="204">Named configuration updated.</response> + /// <returns>Update status.</returns> + [HttpPost("Configuration/{key}")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult UpdateNamedConfiguration([FromRoute, Required] string key, [FromBody, Required] JsonDocument configuration) + { + var configurationType = _configurationManager.GetConfigurationType(key); + var deserializedConfiguration = configuration.Deserialize(configurationType, _serializerOptions); + + if (deserializedConfiguration is null) + { + throw new ArgumentException("Body doesn't contain a valid configuration"); + } + + _configurationManager.SaveConfiguration(key, deserializedConfiguration); + return NoContent(); + } + + /// <summary> + /// Gets a default MetadataOptions object. + /// </summary> + /// <response code="200">Metadata options returned.</response> + /// <returns>Default MetadataOptions.</returns> + [HttpGet("Configuration/MetadataOptions/Default")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<MetadataOptions> GetDefaultMetadataOptions() + { + return new MetadataOptions(); + } + + /// <summary> + /// Updates branding configuration. + /// </summary> + /// <param name="configuration">Branding configuration.</param> + /// <response code="204">Branding configuration updated.</response> + /// <returns>Update status.</returns> + [HttpPost("Configuration/Branding")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult UpdateBrandingConfiguration([FromBody, Required] BrandingOptionsDto configuration) + { + // Get the current branding configuration to preserve SplashscreenLocation + var currentBranding = (BrandingOptions)_configurationManager.GetConfiguration("branding"); + + // Update only the properties from BrandingOptionsDto + currentBranding.LoginDisclaimer = configuration.LoginDisclaimer; + currentBranding.CustomCss = configuration.CustomCss; + currentBranding.SplashscreenEnabled = configuration.SplashscreenEnabled; + + _configurationManager.SaveConfiguration("branding", currentBranding); + + return NoContent(); + } +} diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs new file mode 100644 index 00000000..ee912a9b --- /dev/null +++ b/Jellyfin.Api/Controllers/DashboardController.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Mime; +using Jellyfin.Api.Attributes; +using Jellyfin.Api.Models; +using MediaBrowser.Common.Api; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Plugins; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The dashboard controller. +/// </summary> +[Route("")] +public class DashboardController : BaseJellyfinApiController +{ + private readonly ILogger<DashboardController> _logger; + private readonly IPluginManager _pluginManager; + + /// <summary> + /// Initializes a new instance of the <see cref="DashboardController"/> class. + /// </summary> + /// <param name="logger">Instance of <see cref="ILogger{DashboardController}"/> interface.</param> + /// <param name="pluginManager">Instance of <see cref="IPluginManager"/> interface.</param> + public DashboardController( + ILogger<DashboardController> logger, + IPluginManager pluginManager) + { + _logger = logger; + _pluginManager = pluginManager; + } + + /// <summary> + /// Gets the configuration pages. + /// </summary> + /// <param name="enableInMainMenu">Whether to enable in the main menu.</param> + /// <response code="200">ConfigurationPages returned.</response> + /// <response code="404">Server still loading.</response> + /// <returns>An <see cref="IEnumerable{ConfigurationPageInfo}"/> with infos about the plugins.</returns> + [HttpGet("web/ConfigurationPages")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<IEnumerable<ConfigurationPageInfo>> GetConfigurationPages( + [FromQuery] bool? enableInMainMenu) + { + var configPages = _pluginManager.Plugins.SelectMany(GetConfigPages).ToList(); + + if (enableInMainMenu.HasValue) + { + configPages = configPages.Where(p => p.EnableInMainMenu == enableInMainMenu.Value).ToList(); + } + + return configPages; + } + + /// <summary> + /// Gets a dashboard configuration page. + /// </summary> + /// <param name="name">The name of the page.</param> + /// <response code="200">ConfigurationPage returned.</response> + /// <response code="404">Plugin configuration page not found.</response> + /// <returns>The configuration page.</returns> + [HttpGet("web/ConfigurationPage")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesFile(MediaTypeNames.Text.Html, "application/x-javascript")] + public ActionResult GetDashboardConfigurationPage([FromQuery] string? name) + { + var altPage = GetPluginPages().FirstOrDefault(p => string.Equals(p.Item1.Name, name, StringComparison.OrdinalIgnoreCase)); + if (altPage is null) + { + return NotFound(); + } + + IPlugin plugin = altPage.Item2; + string resourcePath = altPage.Item1.EmbeddedResourcePath; + Stream? stream = plugin.GetType().Assembly.GetManifestResourceStream(resourcePath); + if (stream is null) + { + _logger.LogError("Failed to get resource {Resource} from plugin {Plugin}", resourcePath, plugin.Name); + return NotFound(); + } + + return File(stream, MimeTypes.GetMimeType(resourcePath)); + } + + private IEnumerable<ConfigurationPageInfo> GetConfigPages(LocalPlugin plugin) + { + return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin.Instance, i.Item1)); + } + + private IEnumerable<Tuple<PluginPageInfo, IPlugin>> GetPluginPages(LocalPlugin plugin) + { + if (plugin.Instance is not IHasWebPages hasWebPages) + { + return Enumerable.Empty<Tuple<PluginPageInfo, IPlugin>>(); + } + + return hasWebPages.GetPages().Select(i => new Tuple<PluginPageInfo, IPlugin>(i, plugin.Instance)); + } + + private IEnumerable<Tuple<PluginPageInfo, IPlugin>> GetPluginPages() + { + return _pluginManager.Plugins.SelectMany(GetPluginPages); + } +} diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs new file mode 100644 index 00000000..50050262 --- /dev/null +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -0,0 +1,140 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Jellyfin.Api.Helpers; +using Jellyfin.Data.Dtos; +using Jellyfin.Data.Queries; +using MediaBrowser.Common.Api; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Devices Controller. +/// </summary> +[Authorize(Policy = Policies.RequiresElevation)] +public class DevicesController : BaseJellyfinApiController +{ + private readonly IDeviceManager _deviceManager; + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="DevicesController"/> class. + /// </summary> + /// <param name="deviceManager">Instance of <see cref="IDeviceManager"/> interface.</param> + /// <param name="sessionManager">Instance of <see cref="ISessionManager"/> interface.</param> + public DevicesController( + IDeviceManager deviceManager, + ISessionManager sessionManager) + { + _deviceManager = deviceManager; + _sessionManager = sessionManager; + } + + /// <summary> + /// Get Devices. + /// </summary> + /// <param name="userId">Gets or sets the user identifier.</param> + /// <response code="200">Devices retrieved.</response> + /// <returns>An <see cref="OkResult"/> containing the list of devices.</returns> + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<DeviceInfoDto>> GetDevices([FromQuery] Guid? userId) + { + userId = RequestHelpers.GetUserId(User, userId); + return _deviceManager.GetDevicesForUser(userId); + } + + /// <summary> + /// Get info for a device. + /// </summary> + /// <param name="id">Device Id.</param> + /// <response code="200">Device info retrieved.</response> + /// <response code="404">Device not found.</response> + /// <returns>An <see cref="OkResult"/> containing the device info on success, or a <see cref="NotFoundResult"/> if the device could not be found.</returns> + [HttpGet("Info")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<DeviceInfoDto> GetDeviceInfo([FromQuery, Required] string id) + { + var deviceInfo = _deviceManager.GetDevice(id); + if (deviceInfo is null) + { + return NotFound(); + } + + return deviceInfo; + } + + /// <summary> + /// Get options for a device. + /// </summary> + /// <param name="id">Device Id.</param> + /// <response code="200">Device options retrieved.</response> + /// <response code="404">Device not found.</response> + /// <returns>An <see cref="OkResult"/> containing the device info on success, or a <see cref="NotFoundResult"/> if the device could not be found.</returns> + [HttpGet("Options")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<DeviceOptionsDto> GetDeviceOptions([FromQuery, Required] string id) + { + var deviceInfo = _deviceManager.GetDeviceOptions(id); + if (deviceInfo is null) + { + return NotFound(); + } + + return deviceInfo; + } + + /// <summary> + /// Update device options. + /// </summary> + /// <param name="id">Device Id.</param> + /// <param name="deviceOptions">Device Options.</param> + /// <response code="204">Device options updated.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Options")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> UpdateDeviceOptions( + [FromQuery, Required] string id, + [FromBody, Required] DeviceOptionsDto deviceOptions) + { + await _deviceManager.UpdateDeviceOptions(id, deviceOptions.CustomName).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Deletes a device. + /// </summary> + /// <param name="id">Device Id.</param> + /// <response code="204">Device deleted.</response> + /// <response code="404">Device not found.</response> + /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the device could not be found.</returns> + [HttpDelete] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> DeleteDevice([FromQuery, Required] string id) + { + var existingDevice = _deviceManager.GetDevice(id); + if (existingDevice is null) + { + return NotFound(); + } + + var sessions = _deviceManager.GetDevices(new DeviceQuery { DeviceId = id }); + + foreach (var session in sessions.Items) + { + await _sessionManager.Logout(session).ConfigureAwait(false); + } + + return NoContent(); + } +} diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs new file mode 100644 index 00000000..ef54e9db --- /dev/null +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -0,0 +1,222 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using Jellyfin.Api.Helpers; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller; +using MediaBrowser.Model.Dto; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Display Preferences Controller. +/// </summary> +[Authorize] +public class DisplayPreferencesController : BaseJellyfinApiController +{ + private readonly IDisplayPreferencesManager _displayPreferencesManager; + private readonly ILogger<DisplayPreferencesController> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="DisplayPreferencesController"/> class. + /// </summary> + /// <param name="displayPreferencesManager">Instance of <see cref="IDisplayPreferencesManager"/> interface.</param> + /// <param name="logger">Instance of <see cref="ILogger{DisplayPreferencesController}"/> interface.</param> + public DisplayPreferencesController(IDisplayPreferencesManager displayPreferencesManager, ILogger<DisplayPreferencesController> logger) + { + _displayPreferencesManager = displayPreferencesManager; + _logger = logger; + } + + /// <summary> + /// Get Display Preferences. + /// </summary> + /// <param name="displayPreferencesId">Display preferences id.</param> + /// <param name="userId">User id.</param> + /// <param name="client">Client.</param> + /// <response code="200">Display preferences retrieved.</response> + /// <returns>An <see cref="OkResult"/> containing the display preferences on success, or a <see cref="NotFoundResult"/> if the display preferences could not be found.</returns> + [HttpGet("{displayPreferencesId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")] + public ActionResult<DisplayPreferencesDto> GetDisplayPreferences( + [FromRoute, Required] string displayPreferencesId, + [FromQuery] Guid? userId, + [FromQuery, Required] string client) + { + userId = RequestHelpers.GetUserId(User, userId); + + if (!Guid.TryParse(displayPreferencesId, out var itemId)) + { + itemId = displayPreferencesId.GetMD5(); + } + + var displayPreferences = _displayPreferencesManager.GetDisplayPreferences(userId.Value, itemId, client); + var itemPreferences = _displayPreferencesManager.GetItemDisplayPreferences(displayPreferences.UserId, itemId, displayPreferences.Client); + itemPreferences.ItemId = itemId; + + var dto = new DisplayPreferencesDto + { + Client = displayPreferences.Client, + Id = displayPreferences.ItemId.ToString(), + SortBy = itemPreferences.SortBy, + SortOrder = itemPreferences.SortOrder, + IndexBy = displayPreferences.IndexBy?.ToString(), + RememberIndexing = itemPreferences.RememberIndexing, + RememberSorting = itemPreferences.RememberSorting, + ScrollDirection = displayPreferences.ScrollDirection, + ShowBackdrop = displayPreferences.ShowBackdrop, + ShowSidebar = displayPreferences.ShowSidebar + }; + + foreach (var homeSection in displayPreferences.HomeSections) + { + dto.CustomPrefs["homesection" + homeSection.Order] = homeSection.Type.ToString().ToLowerInvariant(); + } + + dto.CustomPrefs["chromecastVersion"] = displayPreferences.ChromecastVersion.ToString().ToLowerInvariant(); + dto.CustomPrefs["skipForwardLength"] = displayPreferences.SkipForwardLength.ToString(CultureInfo.InvariantCulture); + dto.CustomPrefs["skipBackLength"] = displayPreferences.SkipBackwardLength.ToString(CultureInfo.InvariantCulture); + dto.CustomPrefs["enableNextVideoInfoOverlay"] = displayPreferences.EnableNextVideoInfoOverlay.ToString(CultureInfo.InvariantCulture); + dto.CustomPrefs["tvhome"] = displayPreferences.TvHome; + dto.CustomPrefs["dashboardTheme"] = displayPreferences.DashboardTheme; + + // Load all custom display preferences + var customDisplayPreferences = _displayPreferencesManager.ListCustomItemDisplayPreferences(displayPreferences.UserId, itemId, displayPreferences.Client); + foreach (var (key, value) in customDisplayPreferences) + { + dto.CustomPrefs.TryAdd(key, value); + } + + return dto; + } + + /// <summary> + /// Update Display Preferences. + /// </summary> + /// <param name="displayPreferencesId">Display preferences id.</param> + /// <param name="userId">User Id.</param> + /// <param name="client">Client.</param> + /// <param name="displayPreferences">New Display Preferences object.</param> + /// <response code="204">Display preferences updated.</response> + /// <returns>An <see cref="NoContentResult"/> on success.</returns> + [HttpPost("{displayPreferencesId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")] + public ActionResult UpdateDisplayPreferences( + [FromRoute, Required] string displayPreferencesId, + [FromQuery] Guid? userId, + [FromQuery, Required] string client, + [FromBody, Required] DisplayPreferencesDto displayPreferences) + { + userId = RequestHelpers.GetUserId(User, userId); + + HomeSectionType[] defaults = + { + HomeSectionType.SmallLibraryTiles, + HomeSectionType.Resume, + HomeSectionType.ResumeAudio, + HomeSectionType.ResumeBook, + HomeSectionType.LiveTv, + HomeSectionType.NextUp, + HomeSectionType.LatestMedia, + HomeSectionType.None, + }; + + if (!Guid.TryParse(displayPreferencesId, out var itemId)) + { + itemId = displayPreferencesId.GetMD5(); + } + + var existingDisplayPreferences = _displayPreferencesManager.GetDisplayPreferences(userId.Value, itemId, client); + existingDisplayPreferences.IndexBy = Enum.TryParse<IndexingKind>(displayPreferences.IndexBy, true, out var indexBy) ? indexBy : null; + existingDisplayPreferences.ShowBackdrop = displayPreferences.ShowBackdrop; + existingDisplayPreferences.ShowSidebar = displayPreferences.ShowSidebar; + + existingDisplayPreferences.ScrollDirection = displayPreferences.ScrollDirection; + existingDisplayPreferences.ChromecastVersion = displayPreferences.CustomPrefs.TryGetValue("chromecastVersion", out var chromecastVersion) + && !string.IsNullOrEmpty(chromecastVersion) + ? Enum.Parse<ChromecastVersion>(chromecastVersion, true) + : ChromecastVersion.Stable; + displayPreferences.CustomPrefs.Remove("chromecastVersion"); + + existingDisplayPreferences.EnableNextVideoInfoOverlay = !displayPreferences.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enableNextVideoInfoOverlay) + || string.IsNullOrEmpty(enableNextVideoInfoOverlay) + || bool.Parse(enableNextVideoInfoOverlay); + displayPreferences.CustomPrefs.Remove("enableNextVideoInfoOverlay"); + + existingDisplayPreferences.SkipBackwardLength = displayPreferences.CustomPrefs.TryGetValue("skipBackLength", out var skipBackLength) + && !string.IsNullOrEmpty(skipBackLength) + ? int.Parse(skipBackLength, CultureInfo.InvariantCulture) + : 10000; + displayPreferences.CustomPrefs.Remove("skipBackLength"); + + existingDisplayPreferences.SkipForwardLength = displayPreferences.CustomPrefs.TryGetValue("skipForwardLength", out var skipForwardLength) + && !string.IsNullOrEmpty(skipForwardLength) + ? int.Parse(skipForwardLength, CultureInfo.InvariantCulture) + : 30000; + displayPreferences.CustomPrefs.Remove("skipForwardLength"); + + existingDisplayPreferences.DashboardTheme = displayPreferences.CustomPrefs.TryGetValue("dashboardTheme", out var theme) + ? theme + : string.Empty; + displayPreferences.CustomPrefs.Remove("dashboardTheme"); + + existingDisplayPreferences.TvHome = displayPreferences.CustomPrefs.TryGetValue("tvhome", out var home) + ? home + : string.Empty; + displayPreferences.CustomPrefs.Remove("tvhome"); + + existingDisplayPreferences.HomeSections.Clear(); + + foreach (var key in displayPreferences.CustomPrefs.Keys.Where(key => key.StartsWith("homesection", StringComparison.OrdinalIgnoreCase))) + { + var order = int.Parse(key.AsSpan().Slice("homesection".Length), CultureInfo.InvariantCulture); + if (!Enum.TryParse<HomeSectionType>(displayPreferences.CustomPrefs[key], true, out var type)) + { + type = order < 8 ? defaults[order] : HomeSectionType.None; + } + + displayPreferences.CustomPrefs.Remove(key); + existingDisplayPreferences.HomeSections.Add(new HomeSection { Order = order, Type = type }); + } + + foreach (var key in displayPreferences.CustomPrefs.Keys.Where(key => key.StartsWith("landing-", StringComparison.OrdinalIgnoreCase))) + { + var viewType = displayPreferences.CustomPrefs[key]; + + if (string.IsNullOrEmpty(viewType)) + { + displayPreferences.CustomPrefs.Remove(key); + continue; + } + + if (!Enum.TryParse<ViewType>(viewType, true, out _)) + { + _logger.LogError("Invalid ViewType: {LandingScreenOption}", viewType); + displayPreferences.CustomPrefs.Remove(key); + } + } + + var itemPrefs = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, itemId, existingDisplayPreferences.Client); + itemPrefs.SortBy = displayPreferences.SortBy ?? "SortName"; + itemPrefs.SortOrder = displayPreferences.SortOrder; + itemPrefs.RememberIndexing = displayPreferences.RememberIndexing; + itemPrefs.RememberSorting = displayPreferences.RememberSorting; + itemPrefs.ItemId = itemId; + + // Set all remaining custom preferences. + _displayPreferencesManager.SetCustomItemDisplayPreferences(userId.Value, itemId, existingDisplayPreferences.Client, displayPreferences.CustomPrefs); + _displayPreferencesManager.UpdateItemDisplayPreferences(itemPrefs); + _displayPreferencesManager.UpdateDisplayPreferences(existingDisplayPreferences); + return NoContent(); + } +} diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs new file mode 100644 index 00000000..f80b36c3 --- /dev/null +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -0,0 +1,2081 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Attributes; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.Models.StreamingDtos; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions; +using Jellyfin.MediaEncoding.Hls.Playlist; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; +using MediaBrowser.MediaEncoding.Encoder; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Dynamic hls controller. +/// </summary> +[Route("")] +[Authorize] +public class DynamicHlsController : BaseJellyfinApiController +{ + private const EncoderPreset DefaultVodEncoderPreset = EncoderPreset.veryfast; + private const EncoderPreset DefaultEventEncoderPreset = EncoderPreset.superfast; + private const TranscodingJobType TranscodingJobType = MediaBrowser.Controller.MediaEncoding.TranscodingJobType.Hls; + + private readonly Version _minFFmpegFlacInMp4 = new Version(6, 0); + private readonly Version _minFFmpegX265BframeInFmp4 = new Version(7, 0, 1); + private readonly Version _minFFmpegHlsSegmentOptions = new Version(5, 0); + + private readonly ILibraryManager _libraryManager; + private readonly IUserManager _userManager; + private readonly IMediaSourceManager _mediaSourceManager; + private readonly IServerConfigurationManager _serverConfigurationManager; + private readonly IMediaEncoder _mediaEncoder; + private readonly IFileSystem _fileSystem; + private readonly ITranscodeManager _transcodeManager; + private readonly ILogger<DynamicHlsController> _logger; + private readonly EncodingHelper _encodingHelper; + private readonly IDynamicHlsPlaylistGenerator _dynamicHlsPlaylistGenerator; + private readonly DynamicHlsHelper _dynamicHlsHelper; + private readonly EncodingOptions _encodingOptions; + + /// <summary> + /// Initializes a new instance of the <see cref="DynamicHlsController"/> class. + /// </summary> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param> + /// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsController}"/> interface.</param> + /// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param> + /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param> + /// <param name="dynamicHlsPlaylistGenerator">Instance of <see cref="IDynamicHlsPlaylistGenerator"/>.</param> + public DynamicHlsController( + ILibraryManager libraryManager, + IUserManager userManager, + IMediaSourceManager mediaSourceManager, + IServerConfigurationManager serverConfigurationManager, + IMediaEncoder mediaEncoder, + IFileSystem fileSystem, + ITranscodeManager transcodeManager, + ILogger<DynamicHlsController> logger, + DynamicHlsHelper dynamicHlsHelper, + EncodingHelper encodingHelper, + IDynamicHlsPlaylistGenerator dynamicHlsPlaylistGenerator) + { + _libraryManager = libraryManager; + _userManager = userManager; + _mediaSourceManager = mediaSourceManager; + _serverConfigurationManager = serverConfigurationManager; + _mediaEncoder = mediaEncoder; + _fileSystem = fileSystem; + _transcodeManager = transcodeManager; + _logger = logger; + _dynamicHlsHelper = dynamicHlsHelper; + _encodingHelper = encodingHelper; + _dynamicHlsPlaylistGenerator = dynamicHlsPlaylistGenerator; + + _encodingOptions = serverConfigurationManager.GetEncodingOptions(); + } + + /// <summary> + /// Gets a hls live stream. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="container">The audio container.</param> + /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param> + /// <param name="params">The streaming parameters.</param> + /// <param name="tag">The tag.</param> + /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param> + /// <param name="playSessionId">The play session id.</param> + /// <param name="segmentContainer">The segment container.</param> + /// <param name="segmentLength">The segment length.</param> + /// <param name="minSegments">The minimum number of segments.</param> + /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> + /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param> + /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> + /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> + /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> + /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param> + /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param> + /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param> + /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param> + /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param> + /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param> + /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param> + /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param> + /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param> + /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param> + /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param> + /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param> + /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param> + /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param> + /// <param name="maxRefFrames">Optional.</param> + /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param> + /// <param name="requireAvc">Optional. Whether to require avc.</param> + /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param> + /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param> + /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param> + /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> + /// <param name="liveStreamId">The live stream id.</param> + /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param> + /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> + /// <param name="transcodeReasons">Optional. The transcoding reason.</param> + /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> + /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param> + /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param> + /// <param name="streamOptions">Optional. The streaming options.</param> + /// <param name="maxWidth">Optional. The max width.</param> + /// <param name="maxHeight">Optional. The max height.</param> + /// <param name="enableSubtitlesInManifest">Optional. Whether to enable subtitles in the manifest.</param> + /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param> + /// <param name="alwaysBurnInSubtitleWhenTranscoding">Whether to always burn in subtitles when transcoding.</param> + /// <response code="200">Hls live stream retrieved.</response> + /// <returns>A <see cref="FileResult"/> containing the hls file.</returns> + [HttpGet("Videos/{itemId}/live.m3u8")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesPlaylistFile] + public async Task<ActionResult> GetLiveHlsStream( + [FromRoute, Required] Guid itemId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? container, + [FromQuery] bool? @static, + [FromQuery] string? @params, + [FromQuery] string? tag, + [FromQuery, ParameterObsolete] string? deviceProfileId, + [FromQuery] string? playSessionId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer, + [FromQuery] int? segmentLength, + [FromQuery] int? minSegments, + [FromQuery] string? mediaSourceId, + [FromQuery] string? deviceId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec, + [FromQuery] bool? enableAutoStreamCopy, + [FromQuery] bool? allowVideoStreamCopy, + [FromQuery] bool? allowAudioStreamCopy, + [FromQuery] int? audioSampleRate, + [FromQuery] int? maxAudioBitDepth, + [FromQuery] int? audioBitRate, + [FromQuery] int? audioChannels, + [FromQuery] int? maxAudioChannels, + [FromQuery] string? profile, + [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level, + [FromQuery] float? framerate, + [FromQuery] float? maxFramerate, + [FromQuery] bool? copyTimestamps, + [FromQuery] long? startTimeTicks, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? videoBitRate, + [FromQuery] int? subtitleStreamIndex, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, + [FromQuery] int? maxRefFrames, + [FromQuery] int? maxVideoBitDepth, + [FromQuery] bool? requireAvc, + [FromQuery] bool? deInterlace, + [FromQuery] bool? requireNonAnamorphic, + [FromQuery] int? transcodingMaxAudioChannels, + [FromQuery] int? cpuCoreLimit, + [FromQuery] string? liveStreamId, + [FromQuery] bool? enableMpegtsM2TsMode, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec, + [FromQuery] string? transcodeReasons, + [FromQuery] int? audioStreamIndex, + [FromQuery] int? videoStreamIndex, + [FromQuery] EncodingContext? context, + [FromQuery] Dictionary<string, string> streamOptions, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] bool? enableSubtitlesInManifest, + [FromQuery] bool enableAudioVbrEncoding = true, + [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false) + { + VideoRequestDto streamingRequest = new VideoRequestDto + { + Id = itemId, + Container = container, + Static = @static ?? false, + Params = @params, + Tag = tag, + PlaySessionId = playSessionId, + SegmentContainer = segmentContainer, + SegmentLength = segmentLength, + MinSegments = minSegments, + MediaSourceId = mediaSourceId, + DeviceId = deviceId, + AudioCodec = audioCodec, + EnableAutoStreamCopy = enableAutoStreamCopy ?? true, + AllowAudioStreamCopy = allowAudioStreamCopy ?? true, + AllowVideoStreamCopy = allowVideoStreamCopy ?? true, + AudioSampleRate = audioSampleRate, + MaxAudioChannels = maxAudioChannels, + AudioBitRate = audioBitRate, + MaxAudioBitDepth = maxAudioBitDepth, + AudioChannels = audioChannels, + Profile = profile, + Level = level, + Framerate = framerate, + MaxFramerate = maxFramerate, + CopyTimestamps = copyTimestamps ?? false, + StartTimeTicks = startTimeTicks, + Width = width, + Height = height, + VideoBitRate = videoBitRate, + SubtitleStreamIndex = subtitleStreamIndex, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External, + MaxRefFrames = maxRefFrames, + MaxVideoBitDepth = maxVideoBitDepth, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, + TranscodingMaxAudioChannels = transcodingMaxAudioChannels, + CpuCoreLimit = cpuCoreLimit, + LiveStreamId = liveStreamId, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, + VideoCodec = videoCodec, + SubtitleCodec = subtitleCodec, + TranscodeReasons = transcodeReasons, + AudioStreamIndex = audioStreamIndex, + VideoStreamIndex = videoStreamIndex, + Context = context ?? EncodingContext.Streaming, + StreamOptions = streamOptions, + MaxHeight = maxHeight, + MaxWidth = maxWidth, + EnableSubtitlesInManifest = enableSubtitlesInManifest ?? true, + EnableAudioVbrEncoding = enableAudioVbrEncoding, + AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding + }; + + // CTS lifecycle is managed internally. + var cancellationTokenSource = new CancellationTokenSource(); + // Due to CTS.Token calling ThrowIfDisposed (https://github.com/dotnet/runtime/issues/29970) we have to "cache" the token + // since it gets disposed when ffmpeg exits + var cancellationToken = cancellationTokenSource.Token; + var state = await StreamingHelpers.GetStreamingState( + streamingRequest, + HttpContext, + _mediaSourceManager, + _userManager, + _libraryManager, + _serverConfigurationManager, + _mediaEncoder, + _encodingHelper, + _transcodeManager, + TranscodingJobType, + cancellationToken) + .ConfigureAwait(false); + + TranscodingJob? job = null; + var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8"); + + if (!System.IO.File.Exists(playlistPath)) + { + using (await _transcodeManager.LockAsync(playlistPath, cancellationToken).ConfigureAwait(false)) + { + if (!System.IO.File.Exists(playlistPath)) + { + // If the playlist doesn't already exist, startup ffmpeg + try + { + job = await _transcodeManager.StartFfMpeg( + state, + playlistPath, + GetCommandLineArguments(playlistPath, state, true, 0), + Request.HttpContext.User.GetUserId(), + TranscodingJobType, + cancellationTokenSource) + .ConfigureAwait(false); + job.IsLiveOutput = true; + } + catch + { + state.Dispose(); + throw; + } + + minSegments = state.MinSegments; + if (minSegments > 0) + { + await HlsHelpers.WaitForMinimumSegmentCount(playlistPath, minSegments, _logger, cancellationToken).ConfigureAwait(false); + } + } + } + } + + job ??= _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); + + if (job is not null) + { + _transcodeManager.OnTranscodeEndRequest(job); + } + + var playlistText = HlsHelpers.GetLivePlaylistText(playlistPath, state); + + return Content(playlistText, MimeTypes.GetMimeType("playlist.m3u8")); + } + + /// <summary> + /// Gets a video hls playlist stream. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param> + /// <param name="params">The streaming parameters.</param> + /// <param name="tag">The tag.</param> + /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param> + /// <param name="playSessionId">The play session id.</param> + /// <param name="segmentContainer">The segment container.</param> + /// <param name="segmentLength">The segment length.</param> + /// <param name="minSegments">The minimum number of segments.</param> + /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> + /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param> + /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> + /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> + /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> + /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param> + /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param> + /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param> + /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param> + /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param> + /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param> + /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param> + /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param> + /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param> + /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param> + /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param> + /// <param name="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param> + /// <param name="maxHeight">Optional. The maximum vertical resolution of the encoded video.</param> + /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param> + /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param> + /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param> + /// <param name="maxRefFrames">Optional.</param> + /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param> + /// <param name="requireAvc">Optional. Whether to require avc.</param> + /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param> + /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param> + /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param> + /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> + /// <param name="liveStreamId">The live stream id.</param> + /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param> + /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> + /// <param name="transcodeReasons">Optional. The transcoding reason.</param> + /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> + /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param> + /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param> + /// <param name="streamOptions">Optional. The streaming options.</param> + /// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param> + /// <param name="enableTrickplay">Enable trickplay image playlists being added to master playlist.</param> + /// <param name="enableAudioVbrEncoding">Whether to enable Audio Encoding.</param> + /// <param name="alwaysBurnInSubtitleWhenTranscoding">Whether to always burn in subtitles when transcoding.</param> + /// <response code="200">Video stream returned.</response> + /// <returns>A <see cref="FileResult"/> containing the playlist file.</returns> + [HttpGet("Videos/{itemId}/master.m3u8")] + [HttpHead("Videos/{itemId}/master.m3u8", Name = "HeadMasterHlsVideoPlaylist")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesPlaylistFile] + public async Task<ActionResult> GetMasterHlsVideoPlaylist( + [FromRoute, Required] Guid itemId, + [FromQuery] bool? @static, + [FromQuery] string? @params, + [FromQuery] string? tag, + [FromQuery, ParameterObsolete] string? deviceProfileId, + [FromQuery] string? playSessionId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer, + [FromQuery] int? segmentLength, + [FromQuery] int? minSegments, + [FromQuery, Required] string mediaSourceId, + [FromQuery] string? deviceId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec, + [FromQuery] bool? enableAutoStreamCopy, + [FromQuery] bool? allowVideoStreamCopy, + [FromQuery] bool? allowAudioStreamCopy, + [FromQuery] int? audioSampleRate, + [FromQuery] int? maxAudioBitDepth, + [FromQuery] int? audioBitRate, + [FromQuery] int? audioChannels, + [FromQuery] int? maxAudioChannels, + [FromQuery] string? profile, + [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level, + [FromQuery] float? framerate, + [FromQuery] float? maxFramerate, + [FromQuery] bool? copyTimestamps, + [FromQuery] long? startTimeTicks, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] int? videoBitRate, + [FromQuery] int? subtitleStreamIndex, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, + [FromQuery] int? maxRefFrames, + [FromQuery] int? maxVideoBitDepth, + [FromQuery] bool? requireAvc, + [FromQuery] bool? deInterlace, + [FromQuery] bool? requireNonAnamorphic, + [FromQuery] int? transcodingMaxAudioChannels, + [FromQuery] int? cpuCoreLimit, + [FromQuery] string? liveStreamId, + [FromQuery] bool? enableMpegtsM2TsMode, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec, + [FromQuery] string? transcodeReasons, + [FromQuery] int? audioStreamIndex, + [FromQuery] int? videoStreamIndex, + [FromQuery] EncodingContext? context, + [FromQuery] Dictionary<string, string> streamOptions, + [FromQuery] bool enableAdaptiveBitrateStreaming = false, + [FromQuery] bool enableTrickplay = true, + [FromQuery] bool enableAudioVbrEncoding = true, + [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false) + { + var streamingRequest = new HlsVideoRequestDto + { + Id = itemId, + Static = @static ?? false, + Params = @params, + Tag = tag, + PlaySessionId = playSessionId, + SegmentContainer = segmentContainer, + SegmentLength = segmentLength, + MinSegments = minSegments, + MediaSourceId = mediaSourceId, + DeviceId = deviceId, + AudioCodec = audioCodec, + EnableAutoStreamCopy = enableAutoStreamCopy ?? true, + AllowAudioStreamCopy = allowAudioStreamCopy ?? true, + AllowVideoStreamCopy = allowVideoStreamCopy ?? true, + AudioSampleRate = audioSampleRate, + MaxAudioChannels = maxAudioChannels, + AudioBitRate = audioBitRate, + MaxAudioBitDepth = maxAudioBitDepth, + AudioChannels = audioChannels, + Profile = profile, + Level = level, + Framerate = framerate, + MaxFramerate = maxFramerate, + CopyTimestamps = copyTimestamps ?? false, + StartTimeTicks = startTimeTicks, + Width = width, + Height = height, + MaxWidth = maxWidth, + MaxHeight = maxHeight, + VideoBitRate = videoBitRate, + SubtitleStreamIndex = subtitleStreamIndex, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External, + MaxRefFrames = maxRefFrames, + MaxVideoBitDepth = maxVideoBitDepth, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, + TranscodingMaxAudioChannels = transcodingMaxAudioChannels, + CpuCoreLimit = cpuCoreLimit, + LiveStreamId = liveStreamId, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, + VideoCodec = videoCodec, + SubtitleCodec = subtitleCodec, + TranscodeReasons = transcodeReasons, + AudioStreamIndex = audioStreamIndex, + VideoStreamIndex = videoStreamIndex, + Context = context ?? EncodingContext.Streaming, + StreamOptions = streamOptions, + EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming, + EnableTrickplay = enableTrickplay, + EnableAudioVbrEncoding = enableAudioVbrEncoding, + AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding + }; + + return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false); + } + + /// <summary> + /// Gets an audio hls playlist stream. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param> + /// <param name="params">The streaming parameters.</param> + /// <param name="tag">The tag.</param> + /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param> + /// <param name="playSessionId">The play session id.</param> + /// <param name="segmentContainer">The segment container.</param> + /// <param name="segmentLength">The segment length.</param> + /// <param name="minSegments">The minimum number of segments.</param> + /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> + /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param> + /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> + /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> + /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> + /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param> + /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param> + /// <param name="maxStreamingBitrate">Optional. The maximum streaming bitrate.</param> + /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param> + /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param> + /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param> + /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param> + /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param> + /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param> + /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param> + /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param> + /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param> + /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param> + /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param> + /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param> + /// <param name="maxRefFrames">Optional.</param> + /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param> + /// <param name="requireAvc">Optional. Whether to require avc.</param> + /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param> + /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param> + /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param> + /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> + /// <param name="liveStreamId">The live stream id.</param> + /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param> + /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> + /// <param name="transcodeReasons">Optional. The transcoding reason.</param> + /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> + /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param> + /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param> + /// <param name="streamOptions">Optional. The streaming options.</param> + /// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param> + /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param> + /// <response code="200">Audio stream returned.</response> + /// <returns>A <see cref="FileResult"/> containing the playlist file.</returns> + [HttpGet("Audio/{itemId}/master.m3u8")] + [HttpHead("Audio/{itemId}/master.m3u8", Name = "HeadMasterHlsAudioPlaylist")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesPlaylistFile] + public async Task<ActionResult> GetMasterHlsAudioPlaylist( + [FromRoute, Required] Guid itemId, + [FromQuery] bool? @static, + [FromQuery] string? @params, + [FromQuery] string? tag, + [FromQuery, ParameterObsolete] string? deviceProfileId, + [FromQuery] string? playSessionId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer, + [FromQuery] int? segmentLength, + [FromQuery] int? minSegments, + [FromQuery, Required] string mediaSourceId, + [FromQuery] string? deviceId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec, + [FromQuery] bool? enableAutoStreamCopy, + [FromQuery] bool? allowVideoStreamCopy, + [FromQuery] bool? allowAudioStreamCopy, + [FromQuery] int? audioSampleRate, + [FromQuery] int? maxAudioBitDepth, + [FromQuery] int? maxStreamingBitrate, + [FromQuery] int? audioBitRate, + [FromQuery] int? audioChannels, + [FromQuery] int? maxAudioChannels, + [FromQuery] string? profile, + [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level, + [FromQuery] float? framerate, + [FromQuery] float? maxFramerate, + [FromQuery] bool? copyTimestamps, + [FromQuery] long? startTimeTicks, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? videoBitRate, + [FromQuery] int? subtitleStreamIndex, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, + [FromQuery] int? maxRefFrames, + [FromQuery] int? maxVideoBitDepth, + [FromQuery] bool? requireAvc, + [FromQuery] bool? deInterlace, + [FromQuery] bool? requireNonAnamorphic, + [FromQuery] int? transcodingMaxAudioChannels, + [FromQuery] int? cpuCoreLimit, + [FromQuery] string? liveStreamId, + [FromQuery] bool? enableMpegtsM2TsMode, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec, + [FromQuery] string? transcodeReasons, + [FromQuery] int? audioStreamIndex, + [FromQuery] int? videoStreamIndex, + [FromQuery] EncodingContext? context, + [FromQuery] Dictionary<string, string> streamOptions, + [FromQuery] bool enableAdaptiveBitrateStreaming = false, + [FromQuery] bool enableAudioVbrEncoding = true) + { + var streamingRequest = new HlsAudioRequestDto + { + Id = itemId, + Static = @static ?? false, + Params = @params, + Tag = tag, + PlaySessionId = playSessionId, + SegmentContainer = segmentContainer, + SegmentLength = segmentLength, + MinSegments = minSegments, + MediaSourceId = mediaSourceId, + DeviceId = deviceId, + AudioCodec = audioCodec, + EnableAutoStreamCopy = enableAutoStreamCopy ?? true, + AllowAudioStreamCopy = allowAudioStreamCopy ?? true, + AllowVideoStreamCopy = allowVideoStreamCopy ?? true, + AudioSampleRate = audioSampleRate, + MaxAudioChannels = maxAudioChannels, + AudioBitRate = audioBitRate ?? maxStreamingBitrate, + MaxAudioBitDepth = maxAudioBitDepth, + AudioChannels = audioChannels, + Profile = profile, + Level = level, + Framerate = framerate, + MaxFramerate = maxFramerate, + CopyTimestamps = copyTimestamps ?? false, + StartTimeTicks = startTimeTicks, + Width = width, + Height = height, + VideoBitRate = videoBitRate, + SubtitleStreamIndex = subtitleStreamIndex, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External, + MaxRefFrames = maxRefFrames, + MaxVideoBitDepth = maxVideoBitDepth, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, + TranscodingMaxAudioChannels = transcodingMaxAudioChannels, + CpuCoreLimit = cpuCoreLimit, + LiveStreamId = liveStreamId, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, + VideoCodec = videoCodec, + SubtitleCodec = subtitleCodec, + TranscodeReasons = transcodeReasons, + AudioStreamIndex = audioStreamIndex, + VideoStreamIndex = videoStreamIndex, + Context = context ?? EncodingContext.Streaming, + StreamOptions = streamOptions, + EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming, + EnableAudioVbrEncoding = enableAudioVbrEncoding, + AlwaysBurnInSubtitleWhenTranscoding = false + }; + + return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false); + } + + /// <summary> + /// Gets a video stream using HTTP live streaming. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param> + /// <param name="params">The streaming parameters.</param> + /// <param name="tag">The tag.</param> + /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param> + /// <param name="playSessionId">The play session id.</param> + /// <param name="segmentContainer">The segment container.</param> + /// <param name="segmentLength">The segment length.</param> + /// <param name="minSegments">The minimum number of segments.</param> + /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> + /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param> + /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> + /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> + /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> + /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param> + /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param> + /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param> + /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param> + /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param> + /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param> + /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param> + /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param> + /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param> + /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param> + /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param> + /// <param name="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param> + /// <param name="maxHeight">Optional. The maximum vertical resolution of the encoded video.</param> + /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param> + /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param> + /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param> + /// <param name="maxRefFrames">Optional.</param> + /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param> + /// <param name="requireAvc">Optional. Whether to require avc.</param> + /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param> + /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param> + /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param> + /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> + /// <param name="liveStreamId">The live stream id.</param> + /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param> + /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> + /// <param name="transcodeReasons">Optional. The transcoding reason.</param> + /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> + /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param> + /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param> + /// <param name="streamOptions">Optional. The streaming options.</param> + /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param> + /// <param name="alwaysBurnInSubtitleWhenTranscoding">Whether to always burn in subtitles when transcoding.</param> + /// <response code="200">Video stream returned.</response> + /// <returns>A <see cref="FileResult"/> containing the audio file.</returns> + [HttpGet("Videos/{itemId}/main.m3u8")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesPlaylistFile] + public async Task<ActionResult> GetVariantHlsVideoPlaylist( + [FromRoute, Required] Guid itemId, + [FromQuery] bool? @static, + [FromQuery] string? @params, + [FromQuery] string? tag, + [FromQuery, ParameterObsolete] string? deviceProfileId, + [FromQuery] string? playSessionId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer, + [FromQuery] int? segmentLength, + [FromQuery] int? minSegments, + [FromQuery] string? mediaSourceId, + [FromQuery] string? deviceId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec, + [FromQuery] bool? enableAutoStreamCopy, + [FromQuery] bool? allowVideoStreamCopy, + [FromQuery] bool? allowAudioStreamCopy, + [FromQuery] int? audioSampleRate, + [FromQuery] int? maxAudioBitDepth, + [FromQuery] int? audioBitRate, + [FromQuery] int? audioChannels, + [FromQuery] int? maxAudioChannels, + [FromQuery] string? profile, + [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level, + [FromQuery] float? framerate, + [FromQuery] float? maxFramerate, + [FromQuery] bool? copyTimestamps, + [FromQuery] long? startTimeTicks, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] int? videoBitRate, + [FromQuery] int? subtitleStreamIndex, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, + [FromQuery] int? maxRefFrames, + [FromQuery] int? maxVideoBitDepth, + [FromQuery] bool? requireAvc, + [FromQuery] bool? deInterlace, + [FromQuery] bool? requireNonAnamorphic, + [FromQuery] int? transcodingMaxAudioChannels, + [FromQuery] int? cpuCoreLimit, + [FromQuery] string? liveStreamId, + [FromQuery] bool? enableMpegtsM2TsMode, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec, + [FromQuery] string? transcodeReasons, + [FromQuery] int? audioStreamIndex, + [FromQuery] int? videoStreamIndex, + [FromQuery] EncodingContext? context, + [FromQuery] Dictionary<string, string> streamOptions, + [FromQuery] bool enableAudioVbrEncoding = true, + [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false) + { + using var cancellationTokenSource = new CancellationTokenSource(); + var streamingRequest = new VideoRequestDto + { + Id = itemId, + Static = @static ?? false, + Params = @params, + Tag = tag, + PlaySessionId = playSessionId, + SegmentContainer = segmentContainer, + SegmentLength = segmentLength, + MinSegments = minSegments, + MediaSourceId = mediaSourceId, + DeviceId = deviceId, + AudioCodec = audioCodec, + EnableAutoStreamCopy = enableAutoStreamCopy ?? true, + AllowAudioStreamCopy = allowAudioStreamCopy ?? true, + AllowVideoStreamCopy = allowVideoStreamCopy ?? true, + AudioSampleRate = audioSampleRate, + MaxAudioChannels = maxAudioChannels, + AudioBitRate = audioBitRate, + MaxAudioBitDepth = maxAudioBitDepth, + AudioChannels = audioChannels, + Profile = profile, + Level = level, + Framerate = framerate, + MaxFramerate = maxFramerate, + CopyTimestamps = copyTimestamps ?? false, + StartTimeTicks = startTimeTicks, + Width = width, + Height = height, + MaxWidth = maxWidth, + MaxHeight = maxHeight, + VideoBitRate = videoBitRate, + SubtitleStreamIndex = subtitleStreamIndex, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External, + MaxRefFrames = maxRefFrames, + MaxVideoBitDepth = maxVideoBitDepth, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, + TranscodingMaxAudioChannels = transcodingMaxAudioChannels, + CpuCoreLimit = cpuCoreLimit, + LiveStreamId = liveStreamId, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, + VideoCodec = videoCodec, + SubtitleCodec = subtitleCodec, + TranscodeReasons = transcodeReasons, + AudioStreamIndex = audioStreamIndex, + VideoStreamIndex = videoStreamIndex, + Context = context ?? EncodingContext.Streaming, + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding, + AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding + }; + + return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource) + .ConfigureAwait(false); + } + + /// <summary> + /// Gets an audio stream using HTTP live streaming. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param> + /// <param name="params">The streaming parameters.</param> + /// <param name="tag">The tag.</param> + /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param> + /// <param name="playSessionId">The play session id.</param> + /// <param name="segmentContainer">The segment container.</param> + /// <param name="segmentLength">The segment length.</param> + /// <param name="minSegments">The minimum number of segments.</param> + /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> + /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param> + /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> + /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> + /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> + /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param> + /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param> + /// <param name="maxStreamingBitrate">Optional. The maximum streaming bitrate.</param> + /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param> + /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param> + /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param> + /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param> + /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param> + /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param> + /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param> + /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param> + /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param> + /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param> + /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param> + /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param> + /// <param name="maxRefFrames">Optional.</param> + /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param> + /// <param name="requireAvc">Optional. Whether to require avc.</param> + /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param> + /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param> + /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param> + /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> + /// <param name="liveStreamId">The live stream id.</param> + /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param> + /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> + /// <param name="transcodeReasons">Optional. The transcoding reason.</param> + /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> + /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param> + /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param> + /// <param name="streamOptions">Optional. The streaming options.</param> + /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param> + /// <response code="200">Audio stream returned.</response> + /// <returns>A <see cref="FileResult"/> containing the audio file.</returns> + [HttpGet("Audio/{itemId}/main.m3u8")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesPlaylistFile] + public async Task<ActionResult> GetVariantHlsAudioPlaylist( + [FromRoute, Required] Guid itemId, + [FromQuery] bool? @static, + [FromQuery] string? @params, + [FromQuery] string? tag, + [FromQuery, ParameterObsolete] string? deviceProfileId, + [FromQuery] string? playSessionId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer, + [FromQuery] int? segmentLength, + [FromQuery] int? minSegments, + [FromQuery] string? mediaSourceId, + [FromQuery] string? deviceId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec, + [FromQuery] bool? enableAutoStreamCopy, + [FromQuery] bool? allowVideoStreamCopy, + [FromQuery] bool? allowAudioStreamCopy, + [FromQuery] int? audioSampleRate, + [FromQuery] int? maxAudioBitDepth, + [FromQuery] int? maxStreamingBitrate, + [FromQuery] int? audioBitRate, + [FromQuery] int? audioChannels, + [FromQuery] int? maxAudioChannels, + [FromQuery] string? profile, + [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level, + [FromQuery] float? framerate, + [FromQuery] float? maxFramerate, + [FromQuery] bool? copyTimestamps, + [FromQuery] long? startTimeTicks, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? videoBitRate, + [FromQuery] int? subtitleStreamIndex, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, + [FromQuery] int? maxRefFrames, + [FromQuery] int? maxVideoBitDepth, + [FromQuery] bool? requireAvc, + [FromQuery] bool? deInterlace, + [FromQuery] bool? requireNonAnamorphic, + [FromQuery] int? transcodingMaxAudioChannels, + [FromQuery] int? cpuCoreLimit, + [FromQuery] string? liveStreamId, + [FromQuery] bool? enableMpegtsM2TsMode, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec, + [FromQuery] string? transcodeReasons, + [FromQuery] int? audioStreamIndex, + [FromQuery] int? videoStreamIndex, + [FromQuery] EncodingContext? context, + [FromQuery] Dictionary<string, string> streamOptions, + [FromQuery] bool enableAudioVbrEncoding = true) + { + using var cancellationTokenSource = new CancellationTokenSource(); + var streamingRequest = new StreamingRequestDto + { + Id = itemId, + Static = @static ?? false, + Params = @params, + Tag = tag, + PlaySessionId = playSessionId, + SegmentContainer = segmentContainer, + SegmentLength = segmentLength, + MinSegments = minSegments, + MediaSourceId = mediaSourceId, + DeviceId = deviceId, + AudioCodec = audioCodec, + EnableAutoStreamCopy = enableAutoStreamCopy ?? true, + AllowAudioStreamCopy = allowAudioStreamCopy ?? true, + AllowVideoStreamCopy = allowVideoStreamCopy ?? true, + AudioSampleRate = audioSampleRate, + MaxAudioChannels = maxAudioChannels, + AudioBitRate = audioBitRate ?? maxStreamingBitrate, + MaxAudioBitDepth = maxAudioBitDepth, + AudioChannels = audioChannels, + Profile = profile, + Level = level, + Framerate = framerate, + MaxFramerate = maxFramerate, + CopyTimestamps = copyTimestamps ?? false, + StartTimeTicks = startTimeTicks, + Width = width, + Height = height, + VideoBitRate = videoBitRate, + SubtitleStreamIndex = subtitleStreamIndex, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External, + MaxRefFrames = maxRefFrames, + MaxVideoBitDepth = maxVideoBitDepth, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, + TranscodingMaxAudioChannels = transcodingMaxAudioChannels, + CpuCoreLimit = cpuCoreLimit, + LiveStreamId = liveStreamId, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, + VideoCodec = videoCodec, + SubtitleCodec = subtitleCodec, + TranscodeReasons = transcodeReasons, + AudioStreamIndex = audioStreamIndex, + VideoStreamIndex = videoStreamIndex, + Context = context ?? EncodingContext.Streaming, + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding, + AlwaysBurnInSubtitleWhenTranscoding = false + }; + + return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource) + .ConfigureAwait(false); + } + + /// <summary> + /// Gets a video stream using HTTP live streaming. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="playlistId">The playlist id.</param> + /// <param name="segmentId">The segment id.</param> + /// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param> + /// <param name="runtimeTicks">The position of the requested segment in ticks.</param> + /// <param name="actualSegmentLengthTicks">The length of the requested segment in ticks.</param> + /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param> + /// <param name="params">The streaming parameters.</param> + /// <param name="tag">The tag.</param> + /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param> + /// <param name="playSessionId">The play session id.</param> + /// <param name="segmentContainer">The segment container.</param> + /// <param name="segmentLength">The desired segment length.</param> + /// <param name="minSegments">The minimum number of segments.</param> + /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> + /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param> + /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> + /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> + /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> + /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param> + /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param> + /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param> + /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param> + /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param> + /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param> + /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param> + /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param> + /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param> + /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param> + /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param> + /// <param name="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param> + /// <param name="maxHeight">Optional. The maximum vertical resolution of the encoded video.</param> + /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param> + /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param> + /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param> + /// <param name="maxRefFrames">Optional.</param> + /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param> + /// <param name="requireAvc">Optional. Whether to require avc.</param> + /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param> + /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param> + /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param> + /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> + /// <param name="liveStreamId">The live stream id.</param> + /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param> + /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> + /// <param name="transcodeReasons">Optional. The transcoding reason.</param> + /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> + /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param> + /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param> + /// <param name="streamOptions">Optional. The streaming options.</param> + /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param> + /// <param name="alwaysBurnInSubtitleWhenTranscoding">Whether to always burn in subtitles when transcoding.</param> + /// <response code="200">Video stream returned.</response> + /// <returns>A <see cref="FileResult"/> containing the audio file.</returns> + [HttpGet("Videos/{itemId}/hls1/{playlistId}/{segmentId}.{container}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesVideoFile] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")] + public async Task<ActionResult> GetHlsVideoSegment( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] string playlistId, + [FromRoute, Required] int segmentId, + [FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container, + [FromQuery, Required] long runtimeTicks, + [FromQuery, Required] long actualSegmentLengthTicks, + [FromQuery] bool? @static, + [FromQuery] string? @params, + [FromQuery] string? tag, + [FromQuery, ParameterObsolete] string? deviceProfileId, + [FromQuery] string? playSessionId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer, + [FromQuery] int? segmentLength, + [FromQuery] int? minSegments, + [FromQuery] string? mediaSourceId, + [FromQuery] string? deviceId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec, + [FromQuery] bool? enableAutoStreamCopy, + [FromQuery] bool? allowVideoStreamCopy, + [FromQuery] bool? allowAudioStreamCopy, + [FromQuery] int? audioSampleRate, + [FromQuery] int? maxAudioBitDepth, + [FromQuery] int? audioBitRate, + [FromQuery] int? audioChannels, + [FromQuery] int? maxAudioChannels, + [FromQuery] string? profile, + [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level, + [FromQuery] float? framerate, + [FromQuery] float? maxFramerate, + [FromQuery] bool? copyTimestamps, + [FromQuery] long? startTimeTicks, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] int? videoBitRate, + [FromQuery] int? subtitleStreamIndex, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, + [FromQuery] int? maxRefFrames, + [FromQuery] int? maxVideoBitDepth, + [FromQuery] bool? requireAvc, + [FromQuery] bool? deInterlace, + [FromQuery] bool? requireNonAnamorphic, + [FromQuery] int? transcodingMaxAudioChannels, + [FromQuery] int? cpuCoreLimit, + [FromQuery] string? liveStreamId, + [FromQuery] bool? enableMpegtsM2TsMode, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec, + [FromQuery] string? transcodeReasons, + [FromQuery] int? audioStreamIndex, + [FromQuery] int? videoStreamIndex, + [FromQuery] EncodingContext? context, + [FromQuery] Dictionary<string, string> streamOptions, + [FromQuery] bool enableAudioVbrEncoding = true, + [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false) + { + var streamingRequest = new VideoRequestDto + { + Id = itemId, + CurrentRuntimeTicks = runtimeTicks, + ActualSegmentLengthTicks = actualSegmentLengthTicks, + Container = container, + Static = @static ?? false, + Params = @params, + Tag = tag, + PlaySessionId = playSessionId, + SegmentContainer = segmentContainer, + SegmentLength = segmentLength, + MinSegments = minSegments, + MediaSourceId = mediaSourceId, + DeviceId = deviceId, + AudioCodec = audioCodec, + EnableAutoStreamCopy = enableAutoStreamCopy ?? true, + AllowAudioStreamCopy = allowAudioStreamCopy ?? true, + AllowVideoStreamCopy = allowVideoStreamCopy ?? true, + AudioSampleRate = audioSampleRate, + MaxAudioChannels = maxAudioChannels, + AudioBitRate = audioBitRate, + MaxAudioBitDepth = maxAudioBitDepth, + AudioChannels = audioChannels, + Profile = profile, + Level = level, + Framerate = framerate, + MaxFramerate = maxFramerate, + CopyTimestamps = copyTimestamps ?? false, + StartTimeTicks = startTimeTicks, + Width = width, + Height = height, + MaxWidth = maxWidth, + MaxHeight = maxHeight, + VideoBitRate = videoBitRate, + SubtitleStreamIndex = subtitleStreamIndex, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External, + MaxRefFrames = maxRefFrames, + MaxVideoBitDepth = maxVideoBitDepth, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, + TranscodingMaxAudioChannels = transcodingMaxAudioChannels, + CpuCoreLimit = cpuCoreLimit, + LiveStreamId = liveStreamId, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, + VideoCodec = videoCodec, + SubtitleCodec = subtitleCodec, + TranscodeReasons = transcodeReasons, + AudioStreamIndex = audioStreamIndex, + VideoStreamIndex = videoStreamIndex, + Context = context ?? EncodingContext.Streaming, + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding, + AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding + }; + + return await GetDynamicSegment(streamingRequest, segmentId) + .ConfigureAwait(false); + } + + /// <summary> + /// Gets a video stream using HTTP live streaming. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="playlistId">The playlist id.</param> + /// <param name="segmentId">The segment id.</param> + /// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param> + /// <param name="runtimeTicks">The position of the requested segment in ticks.</param> + /// <param name="actualSegmentLengthTicks">The length of the requested segment in ticks.</param> + /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param> + /// <param name="params">The streaming parameters.</param> + /// <param name="tag">The tag.</param> + /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param> + /// <param name="playSessionId">The play session id.</param> + /// <param name="segmentContainer">The segment container.</param> + /// <param name="segmentLength">The segment length.</param> + /// <param name="minSegments">The minimum number of segments.</param> + /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> + /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param> + /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> + /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> + /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> + /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param> + /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param> + /// <param name="maxStreamingBitrate">Optional. The maximum streaming bitrate.</param> + /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param> + /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param> + /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param> + /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param> + /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param> + /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param> + /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param> + /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param> + /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param> + /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param> + /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param> + /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param> + /// <param name="maxRefFrames">Optional.</param> + /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param> + /// <param name="requireAvc">Optional. Whether to require avc.</param> + /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param> + /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param> + /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param> + /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> + /// <param name="liveStreamId">The live stream id.</param> + /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param> + /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> + /// <param name="transcodeReasons">Optional. The transcoding reason.</param> + /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> + /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param> + /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param> + /// <param name="streamOptions">Optional. The streaming options.</param> + /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param> + /// <response code="200">Video stream returned.</response> + /// <returns>A <see cref="FileResult"/> containing the audio file.</returns> + [HttpGet("Audio/{itemId}/hls1/{playlistId}/{segmentId}.{container}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesAudioFile] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")] + public async Task<ActionResult> GetHlsAudioSegment( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] string playlistId, + [FromRoute, Required] int segmentId, + [FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container, + [FromQuery, Required] long runtimeTicks, + [FromQuery, Required] long actualSegmentLengthTicks, + [FromQuery] bool? @static, + [FromQuery] string? @params, + [FromQuery] string? tag, + [FromQuery, ParameterObsolete] string? deviceProfileId, + [FromQuery] string? playSessionId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer, + [FromQuery] int? segmentLength, + [FromQuery] int? minSegments, + [FromQuery] string? mediaSourceId, + [FromQuery] string? deviceId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec, + [FromQuery] bool? enableAutoStreamCopy, + [FromQuery] bool? allowVideoStreamCopy, + [FromQuery] bool? allowAudioStreamCopy, + [FromQuery] int? audioSampleRate, + [FromQuery] int? maxAudioBitDepth, + [FromQuery] int? maxStreamingBitrate, + [FromQuery] int? audioBitRate, + [FromQuery] int? audioChannels, + [FromQuery] int? maxAudioChannels, + [FromQuery] string? profile, + [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level, + [FromQuery] float? framerate, + [FromQuery] float? maxFramerate, + [FromQuery] bool? copyTimestamps, + [FromQuery] long? startTimeTicks, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? videoBitRate, + [FromQuery] int? subtitleStreamIndex, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, + [FromQuery] int? maxRefFrames, + [FromQuery] int? maxVideoBitDepth, + [FromQuery] bool? requireAvc, + [FromQuery] bool? deInterlace, + [FromQuery] bool? requireNonAnamorphic, + [FromQuery] int? transcodingMaxAudioChannels, + [FromQuery] int? cpuCoreLimit, + [FromQuery] string? liveStreamId, + [FromQuery] bool? enableMpegtsM2TsMode, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec, + [FromQuery] string? transcodeReasons, + [FromQuery] int? audioStreamIndex, + [FromQuery] int? videoStreamIndex, + [FromQuery] EncodingContext? context, + [FromQuery] Dictionary<string, string> streamOptions, + [FromQuery] bool enableAudioVbrEncoding = true) + { + var streamingRequest = new StreamingRequestDto + { + Id = itemId, + Container = container, + CurrentRuntimeTicks = runtimeTicks, + ActualSegmentLengthTicks = actualSegmentLengthTicks, + Static = @static ?? false, + Params = @params, + Tag = tag, + PlaySessionId = playSessionId, + SegmentContainer = segmentContainer, + SegmentLength = segmentLength, + MinSegments = minSegments, + MediaSourceId = mediaSourceId, + DeviceId = deviceId, + AudioCodec = audioCodec, + EnableAutoStreamCopy = enableAutoStreamCopy ?? true, + AllowAudioStreamCopy = allowAudioStreamCopy ?? true, + AllowVideoStreamCopy = allowVideoStreamCopy ?? true, + AudioSampleRate = audioSampleRate, + MaxAudioChannels = maxAudioChannels, + AudioBitRate = audioBitRate ?? maxStreamingBitrate, + MaxAudioBitDepth = maxAudioBitDepth, + AudioChannels = audioChannels, + Profile = profile, + Level = level, + Framerate = framerate, + MaxFramerate = maxFramerate, + CopyTimestamps = copyTimestamps ?? false, + StartTimeTicks = startTimeTicks, + Width = width, + Height = height, + VideoBitRate = videoBitRate, + SubtitleStreamIndex = subtitleStreamIndex, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.External, + MaxRefFrames = maxRefFrames, + MaxVideoBitDepth = maxVideoBitDepth, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, + TranscodingMaxAudioChannels = transcodingMaxAudioChannels, + CpuCoreLimit = cpuCoreLimit, + LiveStreamId = liveStreamId, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, + VideoCodec = videoCodec, + SubtitleCodec = subtitleCodec, + TranscodeReasons = transcodeReasons, + AudioStreamIndex = audioStreamIndex, + VideoStreamIndex = videoStreamIndex, + Context = context ?? EncodingContext.Streaming, + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding, + AlwaysBurnInSubtitleWhenTranscoding = false + }; + + return await GetDynamicSegment(streamingRequest, segmentId) + .ConfigureAwait(false); + } + + private async Task<ActionResult> GetVariantPlaylistInternal(StreamingRequestDto streamingRequest, CancellationTokenSource cancellationTokenSource) + { + using var state = await StreamingHelpers.GetStreamingState( + streamingRequest, + HttpContext, + _mediaSourceManager, + _userManager, + _libraryManager, + _serverConfigurationManager, + _mediaEncoder, + _encodingHelper, + _transcodeManager, + TranscodingJobType, + cancellationTokenSource.Token) + .ConfigureAwait(false); + var mediaSourceId = state.BaseRequest.MediaSourceId; + double fps = state.TargetFramerate ?? 0.0f; + int segmentLength = state.SegmentLength * 1000; + + // If framerate is fractional (i.e. 23.976), we need to slightly adjust segment length + if (Math.Abs(fps - Math.Floor(fps + 0.001f)) > 0.001) + { + double nearestIntFramerate = Math.Ceiling(fps); + segmentLength = (int)Math.Ceiling(segmentLength * (nearestIntFramerate / fps)); + } + + var request = new CreateMainPlaylistRequest( + mediaSourceId is null ? null : Guid.Parse(mediaSourceId), + state.MediaPath, + segmentLength, + state.RunTimeTicks ?? 0, + state.Request.SegmentContainer ?? string.Empty, + "hls1/main/", + Request.QueryString.ToString(), + EncodingHelper.IsCopyCodec(state.OutputVideoCodec)); + var playlist = _dynamicHlsPlaylistGenerator.CreateMainPlaylist(request); + + return new FileContentResult(Encoding.UTF8.GetBytes(playlist), MimeTypes.GetMimeType("playlist.m3u8")); + } + + private async Task<ActionResult> GetDynamicSegment(StreamingRequestDto streamingRequest, int segmentId) + { + if ((streamingRequest.StartTimeTicks ?? 0) > 0) + { + throw new ArgumentException("StartTimeTicks is not allowed."); + } + + // CTS lifecycle is managed internally. + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; + + var state = await StreamingHelpers.GetStreamingState( + streamingRequest, + HttpContext, + _mediaSourceManager, + _userManager, + _libraryManager, + _serverConfigurationManager, + _mediaEncoder, + _encodingHelper, + _transcodeManager, + TranscodingJobType, + cancellationToken) + .ConfigureAwait(false); + + var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8"); + + var segmentPath = GetSegmentPath(state, playlistPath, segmentId); + + var segmentExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer); + + TranscodingJob? job; + + if (System.IO.File.Exists(segmentPath)) + { + job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); + _logger.LogDebug("returning {0} [it exists, try 1]", segmentPath); + return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false); + } + + using (await _transcodeManager.LockAsync(playlistPath, cancellationToken).ConfigureAwait(false)) + { + var startTranscoding = false; + if (System.IO.File.Exists(segmentPath)) + { + job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); + _logger.LogDebug("returning {0} [it exists, try 2]", segmentPath); + return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false); + } + + var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension); + var segmentGapRequiringTranscodingChange = 24 / state.SegmentLength; + + if (segmentId == -1) + { + _logger.LogDebug("Starting transcoding because fmp4 init file is being requested"); + startTranscoding = true; + segmentId = 0; + } + else if (currentTranscodingIndex is null) + { + _logger.LogDebug("Starting transcoding because currentTranscodingIndex=null"); + startTranscoding = true; + } + else if (segmentId < currentTranscodingIndex.Value) + { + _logger.LogDebug("Starting transcoding because requestedIndex={0} and currentTranscodingIndex={1}", segmentId, currentTranscodingIndex); + startTranscoding = true; + } + else if (segmentId - currentTranscodingIndex.Value > segmentGapRequiringTranscodingChange) + { + _logger.LogDebug("Starting transcoding because segmentGap is {0} and max allowed gap is {1}. requestedIndex={2}", segmentId - currentTranscodingIndex.Value, segmentGapRequiringTranscodingChange, segmentId); + startTranscoding = true; + } + + if (startTranscoding) + { + // If the playlist doesn't already exist, startup ffmpeg + try + { + await _transcodeManager.KillTranscodingJobs(streamingRequest.DeviceId, streamingRequest.PlaySessionId, p => false) + .ConfigureAwait(false); + + if (currentTranscodingIndex.HasValue) + { + await DeleteLastFile(playlistPath, segmentExtension, 0).ConfigureAwait(false); + } + + streamingRequest.StartTimeTicks = streamingRequest.CurrentRuntimeTicks; + + state.WaitForPath = segmentPath; + job = await _transcodeManager.StartFfMpeg( + state, + playlistPath, + GetCommandLineArguments(playlistPath, state, false, segmentId), + Request.HttpContext.User.GetUserId(), + TranscodingJobType, + cancellationTokenSource).ConfigureAwait(false); + } + catch + { + state.Dispose(); + throw; + } + + // await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false); + } + else + { + job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); + if (job?.TranscodingThrottler is not null) + { + await job.TranscodingThrottler.UnpauseTranscoding().ConfigureAwait(false); + } + } + } + + _logger.LogDebug("returning {0} [general case]", segmentPath); + job ??= _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); + return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false); + } + + private static double[] GetSegmentLengths(StreamState state) + => GetSegmentLengthsInternal(state.RunTimeTicks ?? 0, state.SegmentLength); + + internal static double[] GetSegmentLengthsInternal(long runtimeTicks, int segmentlength) + { + var segmentLengthTicks = TimeSpan.FromSeconds(segmentlength).Ticks; + var wholeSegments = runtimeTicks / segmentLengthTicks; + var remainingTicks = runtimeTicks % segmentLengthTicks; + + var segmentsLen = wholeSegments + (remainingTicks == 0 ? 0 : 1); + var segments = new double[segmentsLen]; + for (int i = 0; i < wholeSegments; i++) + { + segments[i] = segmentlength; + } + + if (remainingTicks != 0) + { + segments[^1] = TimeSpan.FromTicks(remainingTicks).TotalSeconds; + } + + return segments; + } + + private string GetCommandLineArguments(string outputPath, StreamState state, bool isEventPlaylist, int startNumber) + { + var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions); + var threads = EncodingHelper.GetNumberOfThreads(state, _encodingOptions, videoCodec); + + var mapArgs = state.IsOutputVideo ? _encodingHelper.GetMapArgs(state) : string.Empty; + + var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)); + var outputFileNameWithoutExtension = Path.GetFileNameWithoutExtension(outputPath); + var outputPrefix = Path.Combine(directory, outputFileNameWithoutExtension); + var outputExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer); + var outputTsArg = outputPrefix + "%d" + outputExtension; + + var segmentFormat = string.Empty; + var segmentContainer = outputExtension.TrimStart('.'); + var inputModifier = _encodingHelper.GetInputModifier(state, _encodingOptions, segmentContainer); + var hlsArguments = $"-hls_playlist_type {(isEventPlaylist ? "event" : "vod")} -hls_list_size 0"; + + if (string.Equals(segmentContainer, "ts", StringComparison.OrdinalIgnoreCase)) + { + segmentFormat = "mpegts"; + } + else if (string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase)) + { + var outputFmp4HeaderArg = OperatingSystem.IsWindows() switch + { + // on Windows, the path of fmp4 header file needs to be configured + true => " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\"", + // on Linux/Unix, ffmpeg generate fmp4 header file to m3u8 output folder + false => " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\"" + }; + + var useLegacySegmentOption = _mediaEncoder.EncoderVersion < _minFFmpegHlsSegmentOptions; + + if (state.VideoStream is not null && state.IsOutputVideo) + { + // fMP4 needs this flag to write the audio packet DTS/PTS including the initial delay into MOOF::TRAF::TFDT + hlsArguments += $" {(useLegacySegmentOption ? "-hls_ts_options" : "-hls_segment_options")} movflags=+frag_discont"; + } + + segmentFormat = "fmp4" + outputFmp4HeaderArg; + } + else + { + _logger.LogError("Invalid HLS segment container: {SegmentContainer}, default to mpegts", segmentContainer); + segmentFormat = "mpegts"; + } + + var maxMuxingQueueSize = _encodingOptions.MaxMuxingQueueSize > 128 + ? _encodingOptions.MaxMuxingQueueSize.ToString(CultureInfo.InvariantCulture) + : "128"; + + var baseUrlParam = string.Empty; + if (isEventPlaylist) + { + baseUrlParam = string.Format( + CultureInfo.InvariantCulture, + " -hls_base_url \"hls/{0}/\"", + Path.GetFileNameWithoutExtension(outputPath)); + } + + return string.Format( + CultureInfo.InvariantCulture, + "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -hls_segment_type {8} -start_number {9}{10} -hls_segment_filename \"{11}\" {12} -y \"{13}\"", + inputModifier, + _encodingHelper.GetInputArgument(state, _encodingOptions, segmentContainer), + threads, + mapArgs, + GetVideoArguments(state, startNumber, isEventPlaylist, segmentContainer), + GetAudioArguments(state), + maxMuxingQueueSize, + state.SegmentLength.ToString(CultureInfo.InvariantCulture), + segmentFormat, + startNumber.ToString(CultureInfo.InvariantCulture), + baseUrlParam, + EncodingUtils.NormalizePath(outputTsArg), + hlsArguments, + EncodingUtils.NormalizePath(outputPath)).Trim(); + } + + /// <summary> + /// Gets the audio arguments for transcoding. + /// </summary> + /// <param name="state">The <see cref="StreamState"/>.</param> + /// <returns>The command line arguments for audio transcoding.</returns> + private string GetAudioArguments(StreamState state) + { + if (state.AudioStream is null) + { + return string.Empty; + } + + var audioCodec = _encodingHelper.GetAudioEncoder(state); + var bitStreamArgs = _encodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container); + + // opus, dts, truehd and flac (in FFmpeg 5 and older) are experimental in mp4 muxer + var strictArgs = string.Empty; + var actualOutputAudioCodec = state.ActualOutputAudioCodec; + if (string.Equals(actualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase) + || string.Equals(actualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase) + || string.Equals(actualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase) + || (string.Equals(actualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase) + && _mediaEncoder.EncoderVersion < _minFFmpegFlacInMp4)) + { + strictArgs = " -strict -2"; + } + + if (!state.IsOutputVideo) + { + var audioTranscodeParams = string.Empty; + + // -vn to drop any video streams + audioTranscodeParams += "-vn"; + + if (EncodingHelper.IsCopyCodec(audioCodec)) + { + return audioTranscodeParams + " -acodec copy" + bitStreamArgs + strictArgs; + } + + audioTranscodeParams += " -acodec " + audioCodec + bitStreamArgs + strictArgs; + + var audioBitrate = state.OutputAudioBitrate; + var audioChannels = state.OutputAudioChannels; + + if (audioBitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(state.ActualOutputAudioCodec, StringComparison.OrdinalIgnoreCase)) + { + var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, audioBitrate.Value, audioChannels ?? 2); + if (_encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null) + { + audioTranscodeParams += vbrParam; + } + else + { + audioTranscodeParams += " -ab " + audioBitrate.Value.ToString(CultureInfo.InvariantCulture); + } + } + + if (audioChannels.HasValue) + { + audioTranscodeParams += " -ac " + audioChannels.Value.ToString(CultureInfo.InvariantCulture); + } + + if (state.OutputAudioSampleRate.HasValue) + { + audioTranscodeParams += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture); + } + + return audioTranscodeParams; + } + + if (EncodingHelper.IsCopyCodec(audioCodec)) + { + var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions); + var copyArgs = "-codec:a:0 copy" + bitStreamArgs + strictArgs; + + return copyArgs; + } + + var args = "-codec:a:0 " + audioCodec + bitStreamArgs + strictArgs; + + var channels = state.OutputAudioChannels; + + var useDownMixAlgorithm = DownMixAlgorithmsHelper.AlgorithmFilterStrings.ContainsKey((_encodingOptions.DownMixStereoAlgorithm, DownMixAlgorithmsHelper.InferChannelLayout(state.AudioStream))); + + if (channels.HasValue + && (channels.Value != 2 + || (state.AudioStream?.Channels is not null && !useDownMixAlgorithm))) + { + args += " -ac " + channels.Value; + } + + var bitrate = state.OutputAudioBitrate; + if (bitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(actualOutputAudioCodec, StringComparison.OrdinalIgnoreCase)) + { + var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, bitrate.Value, channels ?? 2); + if (_encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null) + { + args += vbrParam; + } + else + { + args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); + } + } + + if (state.OutputAudioSampleRate.HasValue) + { + args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture); + } + else if (state.AudioStream?.CodecTag is not null && state.AudioStream.CodecTag.Equals("ac-4", StringComparison.Ordinal)) + { + // ac-4 audio tends to have a super weird sample rate that will fail most encoders + // force resample it to 48KHz + args += " -ar 48000"; + } + + args += _encodingHelper.GetAudioFilterParam(state, _encodingOptions); + + return args; + } + + /// <summary> + /// Gets the video arguments for transcoding. + /// </summary> + /// <param name="state">The <see cref="StreamState"/>.</param> + /// <param name="startNumber">The first number in the hls sequence.</param> + /// <param name="isEventPlaylist">Whether the playlist is EVENT or VOD.</param> + /// <param name="segmentContainer">The segment container.</param> + /// <returns>The command line arguments for video transcoding.</returns> + private string GetVideoArguments(StreamState state, int startNumber, bool isEventPlaylist, string segmentContainer) + { + if (state.VideoStream is null) + { + return string.Empty; + } + + if (!state.IsOutputVideo) + { + return string.Empty; + } + + var codec = _encodingHelper.GetVideoEncoder(state, _encodingOptions); + + var args = "-codec:v:0 " + codec; + + var isActualOutputVideoCodecAv1 = string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase); + var isActualOutputVideoCodecHevc = string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase); + + if (isActualOutputVideoCodecHevc || isActualOutputVideoCodecAv1) + { + var requestedRange = state.GetRequestedRangeTypes(state.ActualOutputVideoCodec); + // Clients reporting Dolby Vision capabilities with fallbacks may only support the fallback layer. + // Only enable Dolby Vision remuxing if the client explicitly declares support for profiles without fallbacks. + var clientSupportsDoVi = requestedRange.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIgnoreCase); + var videoIsDoVi = EncodingHelper.IsDovi(state.VideoStream); + + if (EncodingHelper.IsCopyCodec(codec) + && (videoIsDoVi && clientSupportsDoVi) + && !_encodingHelper.IsDoviRemoved(state)) + { + if (isActualOutputVideoCodecHevc) + { + // Use hvc1 for 8.4. This is what Dolby uses for its official sample streams. Tagging with dvh1 would break some players with strict tag checking like Apple Safari. + var codecTag = state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHLG ? "hvc1" : "dvh1"; + args += $" -tag:v:0 {codecTag} -strict -2"; + } + else if (isActualOutputVideoCodecAv1) + { + args += " -tag:v:0 dav1 -strict -2"; + } + } + else if (isActualOutputVideoCodecHevc) + { + // Prefer hvc1 to hev1 + args += " -tag:v:0 hvc1"; + } + } + + // if (state.EnableMpegtsM2TsMode) + // { + // args += " -mpegts_m2ts_mode 1"; + // } + + // See if we can save come cpu cycles by avoiding encoding. + if (EncodingHelper.IsCopyCodec(codec)) + { + // If h264_mp4toannexb is ever added, do not use it for live tv. + if (state.VideoStream is not null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) + { + string bitStreamArgs = _encodingHelper.GetBitStreamArgs(state, MediaStreamType.Video); + if (!string.IsNullOrEmpty(bitStreamArgs)) + { + args += " " + bitStreamArgs; + } + } + + args += " -start_at_zero"; + } + else + { + args += _encodingHelper.GetVideoQualityParam(state, codec, _encodingOptions, isEventPlaylist ? DefaultEventEncoderPreset : DefaultVodEncoderPreset); + + // Set the key frame params for video encoding to match the hls segment time. + args += _encodingHelper.GetHlsVideoKeyFrameArguments(state, codec, state.SegmentLength, isEventPlaylist, startNumber); + + // Currently b-frames in libx265 breaks the FMP4-HLS playback on iOS, disable it for now. + if (string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase) + && _mediaEncoder.EncoderVersion < _minFFmpegX265BframeInFmp4) + { + args += " -bf 0"; + } + + // video processing filters. + var videoProcessParam = _encodingHelper.GetVideoProcessingFilterParam(state, _encodingOptions, codec); + + var negativeMapArgs = _encodingHelper.GetNegativeMapArgsByFilters(state, videoProcessParam); + + args = negativeMapArgs + args + videoProcessParam; + + // -start_at_zero is necessary to use with -ss when seeking, + // otherwise the target position cannot be determined. + if (state.SubtitleStream is not null) + { + // Disable start_at_zero for external graphical subs + if (!(state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)) + { + args += " -start_at_zero"; + } + } + } + + // TODO why was this not enabled for VOD? + if (isEventPlaylist && string.Equals(segmentContainer, "ts", StringComparison.OrdinalIgnoreCase)) + { + args += " -flags -global_header"; + } + + if (!string.IsNullOrEmpty(state.OutputVideoSync)) + { + args += EncodingHelper.GetVideoSyncOption(state.OutputVideoSync, _mediaEncoder.EncoderVersion); + } + + args += _encodingHelper.GetOutputFFlags(state); + + return args; + } + + private string GetSegmentPath(StreamState state, string playlist, int index) + { + var folder = Path.GetDirectoryName(playlist) ?? throw new ArgumentException($"Provided path ({playlist}) is not valid.", nameof(playlist)); + var filename = Path.GetFileNameWithoutExtension(playlist); + + return Path.Combine(folder, filename + index.ToString(CultureInfo.InvariantCulture) + EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer)); + } + + private async Task<ActionResult> GetSegmentResult( + StreamState state, + string playlistPath, + string segmentPath, + string segmentExtension, + int segmentIndex, + TranscodingJob? transcodingJob, + CancellationToken cancellationToken) + { + var segmentExists = System.IO.File.Exists(segmentPath); + if (segmentExists) + { + if (transcodingJob is not null && transcodingJob.HasExited) + { + // Transcoding job is over, so assume all existing files are ready + _logger.LogDebug("serving up {0} as transcode is over", segmentPath); + return GetSegmentResult(state, segmentPath, transcodingJob); + } + + var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension); + + // If requested segment is less than transcoding position, we can't transcode backwards, so assume it's ready + if (segmentIndex < currentTranscodingIndex) + { + _logger.LogDebug("serving up {0} as transcode index {1} is past requested point {2}", segmentPath, currentTranscodingIndex, segmentIndex); + return GetSegmentResult(state, segmentPath, transcodingJob); + } + } + + var nextSegmentPath = GetSegmentPath(state, playlistPath, segmentIndex + 1); + if (transcodingJob is not null) + { + while (!cancellationToken.IsCancellationRequested && !transcodingJob.HasExited) + { + // To be considered ready, the segment file has to exist AND + // either the transcoding job should be done or next segment should also exist + if (segmentExists) + { + if (transcodingJob.HasExited || System.IO.File.Exists(nextSegmentPath)) + { + _logger.LogDebug("Serving up {SegmentPath} as it deemed ready", segmentPath); + return GetSegmentResult(state, segmentPath, transcodingJob); + } + } + else + { + segmentExists = System.IO.File.Exists(segmentPath); + if (segmentExists) + { + continue; // avoid unnecessary waiting if segment just became available + } + } + + await Task.Delay(100, cancellationToken).ConfigureAwait(false); + } + + if (!System.IO.File.Exists(segmentPath)) + { + _logger.LogWarning("cannot serve {0} as transcoding quit before we got there", segmentPath); + } + else + { + _logger.LogDebug("serving {0} as it's on disk and transcoding stopped", segmentPath); + } + + cancellationToken.ThrowIfCancellationRequested(); + } + else + { + _logger.LogWarning("cannot serve {0} as it doesn't exist and no transcode is running", segmentPath); + } + + return GetSegmentResult(state, segmentPath, transcodingJob); + } + + private ActionResult GetSegmentResult(StreamState state, string segmentPath, TranscodingJob? transcodingJob) + { + var segmentEndingPositionTicks = state.Request.CurrentRuntimeTicks + state.Request.ActualSegmentLengthTicks; + + Response.OnCompleted(() => + { + _logger.LogDebug("Finished serving {SegmentPath}", segmentPath); + if (transcodingJob is not null) + { + transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks); + _transcodeManager.OnTranscodeEndRequest(transcodingJob); + } + + return Task.CompletedTask; + }); + + return FileStreamResponseHelpers.GetStaticFileResult(segmentPath, MimeTypes.GetMimeType(segmentPath)); + } + + private int? GetCurrentTranscodingIndex(string playlist, string segmentExtension) + { + var job = _transcodeManager.GetTranscodingJob(playlist, TranscodingJobType); + + if (job is null || job.HasExited) + { + return null; + } + + var file = GetLastTranscodingFile(playlist, segmentExtension, _fileSystem); + + if (file is null) + { + return null; + } + + var playlistFilename = Path.GetFileNameWithoutExtension(playlist.AsSpan()); + + var indexString = Path.GetFileNameWithoutExtension(file.Name.AsSpan()).Slice(playlistFilename.Length); + + return int.Parse(indexString, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + + private static FileSystemMetadata? GetLastTranscodingFile(string playlist, string segmentExtension, IFileSystem fileSystem) + { + var folder = Path.GetDirectoryName(playlist) ?? throw new ArgumentException("Path can't be a root directory.", nameof(playlist)); + + var filePrefix = Path.GetFileNameWithoutExtension(playlist); + + try + { + return fileSystem.GetFiles(folder, new[] { segmentExtension }, true, false) + .Where(i => Path.GetFileNameWithoutExtension(i.Name).StartsWith(filePrefix, StringComparison.OrdinalIgnoreCase)) + .MaxBy(fileSystem.GetLastWriteTimeUtc); + } + catch (IOException) + { + return null; + } + } + + private async Task DeleteLastFile(string playlistPath, string segmentExtension, int retryCount) + { + var file = GetLastTranscodingFile(playlistPath, segmentExtension, _fileSystem); + + if (file is null) + { + return; + } + + await DeleteFile(file.FullName, retryCount).ConfigureAwait(false); + } + + private async Task DeleteFile(string path, int retryCount) + { + if (retryCount >= 5) + { + return; + } + + _logger.LogDebug("Deleting partial HLS file {Path}", path); + + try + { + _fileSystem.DeleteFile(path); + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path); + + await Task.Delay(100).ConfigureAwait(false); + await DeleteFile(path, retryCount + 1).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path); + } + } +} diff --git a/Jellyfin.Api/Controllers/EnvironmentController.cs b/Jellyfin.Api/Controllers/EnvironmentController.cs new file mode 100644 index 00000000..794ca969 --- /dev/null +++ b/Jellyfin.Api/Controllers/EnvironmentController.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using Jellyfin.Api.Models.EnvironmentDtos; +using MediaBrowser.Common.Api; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Model.IO; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Environment Controller. +/// </summary> +[Authorize(Policy = Policies.FirstTimeSetupOrElevated)] +public class EnvironmentController : BaseJellyfinApiController +{ + private const char UncSeparator = '\\'; + private const string UncStartPrefix = @"\\"; + + private readonly IFileSystem _fileSystem; + private readonly ILogger<EnvironmentController> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="EnvironmentController"/> class. + /// </summary> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="logger">Instance of the <see cref="ILogger{EnvironmentController}"/> interface.</param> + public EnvironmentController(IFileSystem fileSystem, ILogger<EnvironmentController> logger) + { + _fileSystem = fileSystem; + _logger = logger; + } + + /// <summary> + /// Gets the contents of a given directory in the file system. + /// </summary> + /// <param name="path">The path.</param> + /// <param name="includeFiles">An optional filter to include or exclude files from the results. true/false.</param> + /// <param name="includeDirectories">An optional filter to include or exclude folders from the results. true/false.</param> + /// <response code="200">Directory contents returned.</response> + /// <returns>Directory contents.</returns> + [HttpGet("DirectoryContents")] + [ProducesResponseType(StatusCodes.Status200OK)] + public IEnumerable<FileSystemEntryInfo> GetDirectoryContents( + [FromQuery, Required] string path, + [FromQuery] bool includeFiles = false, + [FromQuery] bool includeDirectories = false) + { + if (path.StartsWith(UncStartPrefix, StringComparison.OrdinalIgnoreCase) + && path.LastIndexOf(UncSeparator) == 1) + { + return Array.Empty<FileSystemEntryInfo>(); + } + + var entries = + _fileSystem.GetFileSystemEntries(path) + .Where(i => (i.IsDirectory && includeDirectories) || (!i.IsDirectory && includeFiles)) + .OrderBy(i => i.FullName); + + return entries.Select(f => new FileSystemEntryInfo(f.Name, f.FullName, f.IsDirectory ? FileSystemEntryType.Directory : FileSystemEntryType.File)); + } + + /// <summary> + /// Validates path. + /// </summary> + /// <param name="validatePathDto">Validate request object.</param> + /// <response code="204">Path validated.</response> + /// <response code="404">Path not found.</response> + /// <returns>Validation status.</returns> + [HttpPost("ValidatePath")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult ValidatePath([FromBody, Required] ValidatePathDto validatePathDto) + { + if (validatePathDto.IsFile.HasValue) + { + if (validatePathDto.IsFile.Value) + { + if (!System.IO.File.Exists(validatePathDto.Path)) + { + return NotFound(); + } + } + else + { + if (!Directory.Exists(validatePathDto.Path)) + { + return NotFound(); + } + } + } + else + { + if (!System.IO.File.Exists(validatePathDto.Path) && !Directory.Exists(validatePathDto.Path)) + { + return NotFound(); + } + + if (validatePathDto.ValidateWritable) + { + if (validatePathDto.Path is null) + { + throw new ResourceNotFoundException(nameof(validatePathDto.Path)); + } + + var file = Path.Combine(validatePathDto.Path, Guid.NewGuid().ToString()); + try + { + System.IO.File.WriteAllText(file, string.Empty); + } + finally + { + if (System.IO.File.Exists(file)) + { + System.IO.File.Delete(file); + } + } + } + } + + return NoContent(); + } + + /// <summary> + /// Gets available drives from the server's file system. + /// </summary> + /// <response code="200">List of entries returned.</response> + /// <returns>List of entries.</returns> + [HttpGet("Drives")] + [ProducesResponseType(StatusCodes.Status200OK)] + public IEnumerable<FileSystemEntryInfo> GetDrives() + { + return _fileSystem.GetDrives().Select(d => new FileSystemEntryInfo(d.Name, d.FullName, FileSystemEntryType.Directory)); + } + + /// <summary> + /// Gets the parent path of a given path. + /// </summary> + /// <param name="path">The path.</param> + /// <returns>Parent path.</returns> + [HttpGet("ParentPath")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<string?> GetParentPath([FromQuery, Required] string path) + { + string? parent = Path.GetDirectoryName(path); + if (string.IsNullOrEmpty(parent)) + { + // Check if unc share + var index = path.LastIndexOf(UncSeparator); + + if (index != -1 && path[0] == UncSeparator) + { + parent = path.Substring(0, index); + + if (string.IsNullOrWhiteSpace(parent.TrimStart(UncSeparator))) + { + parent = null; + } + } + } + + return parent; + } + + /// <summary> + /// Get Default directory browser. + /// </summary> + /// <response code="200">Default directory browser returned.</response> + /// <returns>Default directory browser.</returns> + [HttpGet("DefaultDirectoryBrowser")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<DefaultDirectoryBrowserInfoDto> GetDefaultDirectoryBrowser() + { + return new DefaultDirectoryBrowserInfoDto(); + } +} diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs new file mode 100644 index 00000000..3f9aa93a --- /dev/null +++ b/Jellyfin.Api/Controllers/FilterController.cs @@ -0,0 +1,218 @@ +using System; +using System.Linq; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Filters controller. +/// </summary> +[Route("")] +[Authorize] +public class FilterController : BaseJellyfinApiController +{ + private readonly ILibraryManager _libraryManager; + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="FilterController"/> class. + /// </summary> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + public FilterController(ILibraryManager libraryManager, IUserManager userManager) + { + _libraryManager = libraryManager; + _userManager = userManager; + } + + /// <summary> + /// Gets legacy query filters. + /// </summary> + /// <param name="userId">Optional. User id.</param> + /// <param name="parentId">Optional. Parent id.</param> + /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param> + /// <param name="mediaTypes">Optional. Filter by MediaType. Allows multiple, comma delimited.</param> + /// <response code="200">Legacy filters retrieved.</response> + /// <returns>Legacy query filters.</returns> + [HttpGet("Items/Filters")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryFiltersLegacy> GetQueryFiltersLegacy( + [FromQuery] Guid? userId, + [FromQuery] Guid? parentId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + + BaseItem? item = null; + if (includeItemTypes.Length != 1 + || !(includeItemTypes[0] == BaseItemKind.BoxSet + || includeItemTypes[0] == BaseItemKind.Playlist + || includeItemTypes[0] == BaseItemKind.Trailer + || includeItemTypes[0] == BaseItemKind.Program)) + { + item = _libraryManager.GetParentItem(parentId, user?.Id); + } + + var query = new InternalItemsQuery + { + User = user, + MediaTypes = mediaTypes, + IncludeItemTypes = includeItemTypes, + Recursive = true, + EnableTotalRecordCount = false, + DtoOptions = new DtoOptions + { + Fields = new[] { ItemFields.Genres, ItemFields.Tags }, + EnableImages = false, + EnableUserData = false + } + }; + + if (item is not Folder folder) + { + return new QueryFiltersLegacy(); + } + + var itemList = folder.GetItemList(query); + return new QueryFiltersLegacy + { + Years = itemList.Select(i => i.ProductionYear ?? -1) + .Where(i => i > 0) + .Distinct() + .Order() + .ToArray(), + + Genres = itemList.SelectMany(i => i.Genres) + .DistinctNames() + .Order() + .ToArray(), + + Tags = itemList + .SelectMany(i => i.Tags) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Order() + .ToArray(), + + OfficialRatings = itemList + .Select(i => i.OfficialRating) + .Where(i => !string.IsNullOrWhiteSpace(i)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Order() + .ToArray() + }; + } + + /// <summary> + /// Gets query filters. + /// </summary> + /// <param name="userId">Optional. User id.</param> + /// <param name="parentId">Optional. Specify this to localize the search to a specific item or folder. Omit to use the root.</param> + /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param> + /// <param name="isAiring">Optional. Is item airing.</param> + /// <param name="isMovie">Optional. Is item movie.</param> + /// <param name="isSports">Optional. Is item sports.</param> + /// <param name="isKids">Optional. Is item kids.</param> + /// <param name="isNews">Optional. Is item news.</param> + /// <param name="isSeries">Optional. Is item series.</param> + /// <param name="recursive">Optional. Search recursive.</param> + /// <response code="200">Filters retrieved.</response> + /// <returns>Query filters.</returns> + [HttpGet("Items/Filters2")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryFilters> GetQueryFilters( + [FromQuery] Guid? userId, + [FromQuery] Guid? parentId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery] bool? isAiring, + [FromQuery] bool? isMovie, + [FromQuery] bool? isSports, + [FromQuery] bool? isKids, + [FromQuery] bool? isNews, + [FromQuery] bool? isSeries, + [FromQuery] bool? recursive) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + + BaseItem? parentItem = null; + if (includeItemTypes.Length == 1 + && (includeItemTypes[0] == BaseItemKind.BoxSet + || includeItemTypes[0] == BaseItemKind.Playlist + || includeItemTypes[0] == BaseItemKind.Trailer + || includeItemTypes[0] == BaseItemKind.Program)) + { + parentItem = null; + } + else if (parentId.HasValue) + { + parentItem = _libraryManager.GetItemById<BaseItem>(parentId.Value); + } + + var filters = new QueryFilters(); + var genreQuery = new InternalItemsQuery(user) + { + IncludeItemTypes = includeItemTypes, + DtoOptions = new DtoOptions + { + Fields = Array.Empty<ItemFields>(), + EnableImages = false, + EnableUserData = false + }, + IsAiring = isAiring, + IsMovie = isMovie, + IsSports = isSports, + IsKids = isKids, + IsNews = isNews, + IsSeries = isSeries + }; + + if ((recursive ?? true) || parentItem is UserView || parentItem is ICollectionFolder) + { + genreQuery.AncestorIds = parentItem is null ? Array.Empty<Guid>() : new[] { parentItem.Id }; + } + else + { + genreQuery.Parent = parentItem; + } + + if (includeItemTypes.Length == 1 + && (includeItemTypes[0] == BaseItemKind.MusicAlbum + || includeItemTypes[0] == BaseItemKind.MusicVideo + || includeItemTypes[0] == BaseItemKind.MusicArtist + || includeItemTypes[0] == BaseItemKind.Audio)) + { + filters.Genres = _libraryManager.GetMusicGenres(genreQuery).Items.Select(i => new NameGuidPair + { + Name = i.Item.Name, + Id = i.Item.Id + }).ToArray(); + } + else + { + filters.Genres = _libraryManager.GetGenres(genreQuery).Items.Select(i => new NameGuidPair + { + Name = i.Item.Name, + Id = i.Item.Id + }).ToArray(); + } + + return filters; + } +} diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs new file mode 100644 index 00000000..456e643f --- /dev/null +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -0,0 +1,208 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Genre = MediaBrowser.Controller.Entities.Genre; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The genres controller. +/// </summary> +[Authorize] +public class GenresController : BaseJellyfinApiController +{ + private readonly IUserManager _userManager; + private readonly ILibraryManager _libraryManager; + private readonly IDtoService _dtoService; + + /// <summary> + /// Initializes a new instance of the <see cref="GenresController"/> class. + /// </summary> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> + public GenresController( + IUserManager userManager, + ILibraryManager libraryManager, + IDtoService dtoService) + { + _userManager = userManager; + _libraryManager = libraryManager; + _dtoService = dtoService; + } + + /// <summary> + /// Gets all genres from a given item, folder, or the entire library. + /// </summary> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="searchTerm">The search term.</param> + /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param> + /// <param name="includeItemTypes">Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited.</param> + /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param> + /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="userId">User id.</param> + /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param> + /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param> + /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param> + /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited.</param> + /// <param name="sortOrder">Sort Order - Ascending,Descending.</param> + /// <param name="enableImages">Optional, include image information in output.</param> + /// <param name="enableTotalRecordCount">Optional. Include total record count.</param> + /// <response code="200">Genres returned.</response> + /// <returns>An <see cref="OkResult"/> containing the queryresult of genres.</returns> + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetGenres( + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] string? searchTerm, + [FromQuery] Guid? parentId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery] bool? isFavorite, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery] Guid? userId, + [FromQuery] string? nameStartsWithOrGreater, + [FromQuery] string? nameStartsWith, + [FromQuery] string? nameLessThan, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, + [FromQuery] bool? enableImages = true, + [FromQuery] bool enableTotalRecordCount = true) + { + userId = RequestHelpers.GetUserId(User, userId); + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes); + + User? user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + + var parentItem = _libraryManager.GetParentItem(parentId, userId); + + var query = new InternalItemsQuery(user) + { + ExcludeItemTypes = excludeItemTypes, + IncludeItemTypes = includeItemTypes, + StartIndex = startIndex, + Limit = limit, + IsFavorite = isFavorite, + NameLessThan = nameLessThan, + NameStartsWith = nameStartsWith, + NameStartsWithOrGreater = nameStartsWithOrGreater, + DtoOptions = dtoOptions, + SearchTerm = searchTerm, + EnableTotalRecordCount = enableTotalRecordCount, + OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder) + }; + + if (parentId.HasValue) + { + if (parentItem is Folder) + { + query.AncestorIds = new[] { parentId.Value }; + } + else + { + query.ItemIds = new[] { parentId.Value }; + } + } + + QueryResult<(BaseItem, ItemCounts)> result; + if (parentItem is ICollectionFolder parentCollectionFolder + && (parentCollectionFolder.CollectionType == CollectionType.music + || parentCollectionFolder.CollectionType == CollectionType.musicvideos)) + { + result = _libraryManager.GetMusicGenres(query); + } + else + { + result = _libraryManager.GetGenres(query); + } + + var shouldIncludeItemTypes = includeItemTypes.Length != 0; + return RequestHelpers.CreateQueryResult(result, dtoOptions, _dtoService, shouldIncludeItemTypes, user); + } + + /// <summary> + /// Gets a genre, by name. + /// </summary> + /// <param name="genreName">The genre name.</param> + /// <param name="userId">The user id.</param> + /// <response code="200">Genres returned.</response> + /// <returns>An <see cref="OkResult"/> containing the genre.</returns> + [HttpGet("{genreName}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<BaseItemDto> GetGenre([FromRoute, Required] string genreName, [FromQuery] Guid? userId) + { + userId = RequestHelpers.GetUserId(User, userId); + var dtoOptions = new DtoOptions(); + + Genre? item; + if (genreName.Contains(BaseItem.SlugChar, StringComparison.OrdinalIgnoreCase)) + { + item = GetItemFromSlugName<Genre>(_libraryManager, genreName, dtoOptions, BaseItemKind.Genre); + } + else + { + item = _libraryManager.GetGenre(genreName); + } + + item ??= new Genre(); + + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + + return _dtoService.GetBaseItemDto(item, dtoOptions, user); + } + + private T? GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions, BaseItemKind baseItemKind) + where T : BaseItem, new() + { + var result = libraryManager.GetItemList(new InternalItemsQuery + { + Name = name.Replace(BaseItem.SlugChar, '&'), + IncludeItemTypes = new[] { baseItemKind }, + DtoOptions = dtoOptions + }).OfType<T>().FirstOrDefault(); + + result ??= libraryManager.GetItemList(new InternalItemsQuery + { + Name = name.Replace(BaseItem.SlugChar, '/'), + IncludeItemTypes = new[] { baseItemKind }, + DtoOptions = dtoOptions + }).OfType<T>().FirstOrDefault(); + + result ??= libraryManager.GetItemList(new InternalItemsQuery + { + Name = name.Replace(BaseItem.SlugChar, '?'), + IncludeItemTypes = new[] { baseItemKind }, + DtoOptions = dtoOptions + }).OfType<T>().FirstOrDefault(); + + return result; + } +} diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs new file mode 100644 index 00000000..1927a332 --- /dev/null +++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs @@ -0,0 +1,191 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Threading.Tasks; +using Jellyfin.Api.Attributes; +using Jellyfin.Api.Helpers; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The hls segment controller. +/// </summary> +[Route("")] +public class HlsSegmentController : BaseJellyfinApiController +{ + private readonly IFileSystem _fileSystem; + private readonly IServerConfigurationManager _serverConfigurationManager; + private readonly ITranscodeManager _transcodeManager; + + /// <summary> + /// Initializes a new instance of the <see cref="HlsSegmentController"/> class. + /// </summary> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param> + public HlsSegmentController( + IFileSystem fileSystem, + IServerConfigurationManager serverConfigurationManager, + ITranscodeManager transcodeManager) + { + _fileSystem = fileSystem; + _serverConfigurationManager = serverConfigurationManager; + _transcodeManager = transcodeManager; + } + + /// <summary> + /// Gets the specified audio segment for an audio item. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="segmentId">The segment id.</param> + /// <response code="200">Hls audio segment returned.</response> + /// <returns>A <see cref="FileStreamResult"/> containing the audio stream.</returns> + // Can't require authentication just yet due to seeing some requests come from Chrome without full query string + // [Authenticated] + [HttpGet("Audio/{itemId}/hls/{segmentId}/stream.mp3", Name = "GetHlsAudioSegmentLegacyMp3")] + [HttpGet("Audio/{itemId}/hls/{segmentId}/stream.aac", Name = "GetHlsAudioSegmentLegacyAac")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesAudioFile] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")] + public ActionResult GetHlsAudioSegmentLegacy([FromRoute, Required] string itemId, [FromRoute, Required] string segmentId) + { + // TODO: Deprecate with new iOS app + var file = string.Concat(segmentId, Path.GetExtension(Request.Path.Value.AsSpan())); + var transcodePath = _serverConfigurationManager.GetTranscodePath(); + file = Path.GetFullPath(Path.Combine(transcodePath, file)); + var fileDir = Path.GetDirectoryName(file); + if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath, StringComparison.InvariantCulture)) + { + return BadRequest("Invalid segment."); + } + + return FileStreamResponseHelpers.GetStaticFileResult(file, MimeTypes.GetMimeType(file)); + } + + /// <summary> + /// Gets a hls video playlist. + /// </summary> + /// <param name="itemId">The video id.</param> + /// <param name="playlistId">The playlist id.</param> + /// <response code="200">Hls video playlist returned.</response> + /// <returns>A <see cref="FileStreamResult"/> containing the playlist.</returns> + [HttpGet("Videos/{itemId}/hls/{playlistId}/stream.m3u8")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesPlaylistFile] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")] + public ActionResult GetHlsPlaylistLegacy([FromRoute, Required] string itemId, [FromRoute, Required] string playlistId) + { + var file = string.Concat(playlistId, Path.GetExtension(Request.Path.Value.AsSpan())); + var transcodePath = _serverConfigurationManager.GetTranscodePath(); + file = Path.GetFullPath(Path.Combine(transcodePath, file)); + var fileDir = Path.GetDirectoryName(file); + if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath, StringComparison.InvariantCulture) + || Path.GetExtension(file.AsSpan()).Equals(".m3u8", StringComparison.OrdinalIgnoreCase)) + { + return BadRequest("Invalid segment."); + } + + return GetFileResult(file, file); + } + + /// <summary> + /// Stops an active encoding. + /// </summary> + /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> + /// <param name="playSessionId">The play session id.</param> + /// <response code="204">Encoding stopped successfully.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpDelete("Videos/ActiveEncodings")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult StopEncodingProcess( + [FromQuery, Required] string deviceId, + [FromQuery, Required] string playSessionId) + { + _transcodeManager.KillTranscodingJobs(deviceId, playSessionId, _ => true); + return NoContent(); + } + + /// <summary> + /// Gets a hls video segment. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="playlistId">The playlist id.</param> + /// <param name="segmentId">The segment id.</param> + /// <param name="segmentContainer">The segment container.</param> + /// <response code="200">Hls video segment returned.</response> + /// <response code="404">Hls segment not found.</response> + /// <returns>A <see cref="FileStreamResult"/> containing the video segment.</returns> + // Can't require authentication just yet due to seeing some requests come from Chrome without full query string + // [Authenticated] + [HttpGet("Videos/{itemId}/hls/{playlistId}/{segmentId}.{segmentContainer}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesVideoFile] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")] + public ActionResult GetHlsVideoSegmentLegacy( + [FromRoute, Required] string itemId, + [FromRoute, Required] string playlistId, + [FromRoute, Required] string segmentId, + [FromRoute, Required] string segmentContainer) + { + var file = string.Concat(segmentId, Path.GetExtension(Request.Path.Value.AsSpan())); + var transcodeFolderPath = _serverConfigurationManager.GetTranscodePath(); + + file = Path.GetFullPath(Path.Combine(transcodeFolderPath, file)); + var fileDir = Path.GetDirectoryName(file); + if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodeFolderPath, StringComparison.InvariantCulture)) + { + return BadRequest("Invalid segment."); + } + + var normalizedPlaylistId = playlistId; + + var filePaths = _fileSystem.GetFilePaths(transcodeFolderPath); + // Add . to start of segment container for future use. + segmentContainer = segmentContainer.Insert(0, "."); + string? playlistPath = null; + foreach (var path in filePaths) + { + var pathExtension = Path.GetExtension(path); + if ((string.Equals(pathExtension, segmentContainer, StringComparison.OrdinalIgnoreCase) + || string.Equals(pathExtension, ".m3u8", StringComparison.OrdinalIgnoreCase)) + && path.Contains(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase)) + { + playlistPath = path; + break; + } + } + + return playlistPath is null + ? NotFound("Hls segment not found.") + : GetFileResult(file, playlistPath); + } + + private ActionResult GetFileResult(string path, string playlistPath) + { + var transcodingJob = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls); + + Response.OnCompleted(() => + { + if (transcodingJob is not null) + { + _transcodeManager.OnTranscodeEndRequest(transcodingJob); + } + + return Task.CompletedTask; + }); + + return FileStreamResponseHelpers.GetStaticFileResult(path, MimeTypes.GetMimeType(path)); + } +} diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs new file mode 100644 index 00000000..cd8132d2 --- /dev/null +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -0,0 +1,2069 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net.Mime; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Attributes; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Extensions; +using MediaBrowser.Common.Api; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Branding; +using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Image controller. +/// </summary> +[Route("")] +public class ImageController : BaseJellyfinApiController +{ + private readonly IUserManager _userManager; + private readonly ILibraryManager _libraryManager; + private readonly IProviderManager _providerManager; + private readonly IImageProcessor _imageProcessor; + private readonly IFileSystem _fileSystem; + private readonly ILogger<ImageController> _logger; + private readonly IServerConfigurationManager _serverConfigurationManager; + private readonly IApplicationPaths _appPaths; + + /// <summary> + /// Initializes a new instance of the <see cref="ImageController"/> class. + /// </summary> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="imageProcessor">Instance of the <see cref="IImageProcessor"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="logger">Instance of the <see cref="ILogger{ImageController}"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param> + public ImageController( + IUserManager userManager, + ILibraryManager libraryManager, + IProviderManager providerManager, + IImageProcessor imageProcessor, + IFileSystem fileSystem, + ILogger<ImageController> logger, + IServerConfigurationManager serverConfigurationManager, + IApplicationPaths appPaths) + { + _userManager = userManager; + _libraryManager = libraryManager; + _providerManager = providerManager; + _imageProcessor = imageProcessor; + _fileSystem = fileSystem; + _logger = logger; + _serverConfigurationManager = serverConfigurationManager; + _appPaths = appPaths; + } + + private static CryptoStream GetFromBase64Stream(Stream inputStream) + => new CryptoStream(inputStream, new FromBase64Transform(), CryptoStreamMode.Read); + + /// <summary> + /// Sets the user image. + /// </summary> + /// <param name="userId">User Id.</param> + /// <response code="204">Image updated.</response> + /// <response code="403">User does not have permission to delete the image.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("UserImage")] + [Authorize] + [AcceptsImageFile] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> PostUserImage( + [FromQuery] Guid? userId) + { + var requestUserId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(requestUserId); + if (user is null) + { + return NotFound(); + } + + if (!RequestHelpers.AssertCanUpdateUser(HttpContext.User, user, true)) + { + return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image."); + } + + if (!TryGetImageExtensionFromContentType(Request.ContentType, out string? extension)) + { + return BadRequest("Incorrect ContentType."); + } + + var stream = GetFromBase64Stream(Request.Body); + await using (stream.ConfigureAwait(false)) + { + // Handle image/png; charset=utf-8 + var mimeType = Request.ContentType?.Split(';').FirstOrDefault(); + var userDataPath = Path.Combine(_serverConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, user.Username); + if (user.ProfileImage is not null) + { + await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false); + } + + user.ProfileImage = new Database.Implementations.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + extension)); + + await _providerManager + .SaveImage(stream, mimeType, user.ProfileImage.Path) + .ConfigureAwait(false); + await _userManager.UpdateUserAsync(user).ConfigureAwait(false); + + return NoContent(); + } + } + + /// <summary> + /// Sets the user image. + /// </summary> + /// <param name="userId">User Id.</param> + /// <param name="imageType">(Unused) Image type.</param> + /// <response code="204">Image updated.</response> + /// <response code="403">User does not have permission to delete the image.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Users/{userId}/Images/{imageType}")] + [Authorize] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + [AcceptsImageFile] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")] + public Task<ActionResult> PostUserImageLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] ImageType imageType) + => PostUserImage(userId); + + /// <summary> + /// Sets the user image. + /// </summary> + /// <param name="userId">User Id.</param> + /// <param name="imageType">(Unused) Image type.</param> + /// <param name="index">(Unused) Image index.</param> + /// <response code="204">Image updated.</response> + /// <response code="403">User does not have permission to delete the image.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Users/{userId}/Images/{imageType}/{index}")] + [Authorize] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + [AcceptsImageFile] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] + public Task<ActionResult> PostUserImageByIndexLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] ImageType imageType, + [FromRoute] int index) + => PostUserImage(userId); + + /// <summary> + /// Delete the user's image. + /// </summary> + /// <param name="userId">User Id.</param> + /// <response code="204">Image deleted.</response> + /// <response code="403">User does not have permission to delete the image.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpDelete("UserImage")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task<ActionResult> DeleteUserImage( + [FromQuery] Guid? userId) + { + var requestUserId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(requestUserId); + if (user is null) + { + return NotFound(); + } + + if (!RequestHelpers.AssertCanUpdateUser(HttpContext.User, user, true)) + { + return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to delete the image."); + } + + if (user.ProfileImage is null) + { + return NoContent(); + } + + try + { + System.IO.File.Delete(user.ProfileImage.Path); + } + catch (IOException e) + { + _logger.LogError(e, "Error deleting user profile image:"); + } + + await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Delete the user's image. + /// </summary> + /// <param name="userId">User Id.</param> + /// <param name="imageType">(Unused) Image type.</param> + /// <param name="index">(Unused) Image index.</param> + /// <response code="204">Image deleted.</response> + /// <response code="403">User does not have permission to delete the image.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpDelete("Users/{userId}/Images/{imageType}")] + [Authorize] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public Task<ActionResult> DeleteUserImageLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] ImageType imageType, + [FromQuery] int? index = null) + => DeleteUserImage(userId); + + /// <summary> + /// Delete the user's image. + /// </summary> + /// <param name="userId">User Id.</param> + /// <param name="imageType">(Unused) Image type.</param> + /// <param name="index">(Unused) Image index.</param> + /// <response code="204">Image deleted.</response> + /// <response code="403">User does not have permission to delete the image.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpDelete("Users/{userId}/Images/{imageType}/{index}")] + [Authorize] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public Task<ActionResult> DeleteUserImageByIndexLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] ImageType imageType, + [FromRoute] int index) + => DeleteUserImage(userId); + + /// <summary> + /// Delete an item's image. + /// </summary> + /// <param name="itemId">Item id.</param> + /// <param name="imageType">Image type.</param> + /// <param name="imageIndex">The image index.</param> + /// <response code="204">Image deleted.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns> + [HttpDelete("Items/{itemId}/Images/{imageType}")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> DeleteItemImage( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] ImageType imageType, + [FromQuery] int? imageIndex) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + await item.DeleteImageAsync(imageType, imageIndex ?? 0).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Delete an item's image. + /// </summary> + /// <param name="itemId">Item id.</param> + /// <param name="imageType">Image type.</param> + /// <param name="imageIndex">The image index.</param> + /// <response code="204">Image deleted.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns> + [HttpDelete("Items/{itemId}/Images/{imageType}/{imageIndex}")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> DeleteItemImageByIndex( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] ImageType imageType, + [FromRoute] int imageIndex) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + await item.DeleteImageAsync(imageType, imageIndex).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Set item image. + /// </summary> + /// <param name="itemId">Item id.</param> + /// <param name="imageType">Image type.</param> + /// <response code="204">Image saved.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns> + [HttpPost("Items/{itemId}/Images/{imageType}")] + [Authorize(Policy = Policies.RequiresElevation)] + [AcceptsImageFile] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] + public async Task<ActionResult> SetItemImage( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] ImageType imageType) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + if (!TryGetImageExtensionFromContentType(Request.ContentType, out _)) + { + return BadRequest("Incorrect ContentType."); + } + + var stream = GetFromBase64Stream(Request.Body); + await using (stream.ConfigureAwait(false)) + { + // Handle image/png; charset=utf-8 + var mimeType = Request.ContentType?.Split(';').FirstOrDefault(); + await _providerManager.SaveImage(item, stream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false); + await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false); + + return NoContent(); + } + } + + /// <summary> + /// Set item image. + /// </summary> + /// <param name="itemId">Item id.</param> + /// <param name="imageType">Image type.</param> + /// <param name="imageIndex">(Unused) Image index.</param> + /// <response code="204">Image saved.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns> + [HttpPost("Items/{itemId}/Images/{imageType}/{imageIndex}")] + [Authorize(Policy = Policies.RequiresElevation)] + [AcceptsImageFile] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] + public async Task<ActionResult> SetItemImageByIndex( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] ImageType imageType, + [FromRoute] int imageIndex) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + if (!TryGetImageExtensionFromContentType(Request.ContentType, out _)) + { + return BadRequest("Incorrect ContentType."); + } + + var stream = GetFromBase64Stream(Request.Body); + await using (stream.ConfigureAwait(false)) + { + // Handle image/png; charset=utf-8 + var mimeType = Request.ContentType?.Split(';').FirstOrDefault(); + await _providerManager.SaveImage(item, stream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false); + await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false); + + return NoContent(); + } + } + + /// <summary> + /// Updates the index for an item image. + /// </summary> + /// <param name="itemId">Item id.</param> + /// <param name="imageType">Image type.</param> + /// <param name="imageIndex">Old image index.</param> + /// <param name="newIndex">New image index.</param> + /// <response code="204">Image index updated.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns> + [HttpPost("Items/{itemId}/Images/{imageType}/{imageIndex}/Index")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> UpdateItemImageIndex( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] ImageType imageType, + [FromRoute, Required] int imageIndex, + [FromQuery, Required] int newIndex) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + await item.SwapImagesAsync(imageType, imageIndex, newIndex).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Get item image infos. + /// </summary> + /// <param name="itemId">Item id.</param> + /// <response code="200">Item images returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>The list of image infos on success, or <see cref="NotFoundResult"/> if item not found.</returns> + [HttpGet("Items/{itemId}/Images")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult<IEnumerable<ImageInfo>>> GetItemImageInfos([FromRoute, Required] Guid itemId) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + var list = new List<ImageInfo>(); + var itemImages = item.ImageInfos; + + if (itemImages.Length == 0) + { + // short-circuit + return list; + } + + await _libraryManager.UpdateImagesAsync(item).ConfigureAwait(false); // this makes sure dimensions and hashes are correct + + foreach (var image in itemImages) + { + if (!item.AllowsMultipleImages(image.Type)) + { + var info = GetImageInfo(item, image, null); + + if (info is not null) + { + list.Add(info); + } + } + } + + foreach (var imageType in itemImages.Select(i => i.Type).Distinct().Where(item.AllowsMultipleImages)) + { + var index = 0; + + // Prevent implicitly captured closure + var currentImageType = imageType; + + foreach (var image in itemImages.Where(i => i.Type == currentImageType)) + { + var info = GetImageInfo(item, image, index); + + if (info is not null) + { + list.Add(info); + } + + index++; + } + } + + return list; + } + + /// <summary> + /// Gets the item's image. + /// </summary> + /// <param name="itemId">Item id.</param> + /// <param name="imageType">Image type.</param> + /// <param name="maxWidth">The maximum image width to return.</param> + /// <param name="maxHeight">The maximum image height to return.</param> + /// <param name="width">The fixed image width to return.</param> + /// <param name="height">The fixed image height to return.</param> + /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> + /// <param name="fillWidth">Width of box to fill.</param> + /// <param name="fillHeight">Height of box to fill.</param> + /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> + /// <param name="format">Optional. The <see cref="ImageFormat"/> of the returned image.</param> + /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> + /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> + /// <param name="blur">Optional. Blur image.</param> + /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> + /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> + /// <param name="imageIndex">Image index.</param> + /// <response code="200">Image stream returned.</response> + /// <response code="404">Item not found.</response> + /// <returns> + /// A <see cref="FileStreamResult"/> containing the file stream on success, + /// or a <see cref="NotFoundResult"/> if item not found. + /// </returns> + [HttpGet("Items/{itemId}/Images/{imageType}")] + [HttpHead("Items/{itemId}/Images/{imageType}", Name = "HeadItemImage")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + public async Task<ActionResult> GetItemImage( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] ImageType imageType, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, + [FromQuery] string? tag, + [FromQuery] ImageFormat? format, + [FromQuery] double? percentPlayed, + [FromQuery] int? unplayedCount, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer, + [FromQuery] int? imageIndex) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + return await GetImageInternal( + itemId, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + fillWidth, + fillHeight, + blur, + backgroundColor, + foregroundLayer, + item) + .ConfigureAwait(false); + } + + /// <summary> + /// Gets the item's image. + /// </summary> + /// <param name="itemId">Item id.</param> + /// <param name="imageType">Image type.</param> + /// <param name="imageIndex">Image index.</param> + /// <param name="maxWidth">The maximum image width to return.</param> + /// <param name="maxHeight">The maximum image height to return.</param> + /// <param name="width">The fixed image width to return.</param> + /// <param name="height">The fixed image height to return.</param> + /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> + /// <param name="fillWidth">Width of box to fill.</param> + /// <param name="fillHeight">Height of box to fill.</param> + /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> + /// <param name="format">Optional. The <see cref="ImageFormat"/> of the returned image.</param> + /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> + /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> + /// <param name="blur">Optional. Blur image.</param> + /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> + /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> + /// <response code="200">Image stream returned.</response> + /// <response code="404">Item not found.</response> + /// <returns> + /// A <see cref="FileStreamResult"/> containing the file stream on success, + /// or a <see cref="NotFoundResult"/> if item not found. + /// </returns> + [HttpGet("Items/{itemId}/Images/{imageType}/{imageIndex}")] + [HttpHead("Items/{itemId}/Images/{imageType}/{imageIndex}", Name = "HeadItemImageByIndex")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + public async Task<ActionResult> GetItemImageByIndex( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] ImageType imageType, + [FromRoute] int imageIndex, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, + [FromQuery] string? tag, + [FromQuery] ImageFormat? format, + [FromQuery] double? percentPlayed, + [FromQuery] int? unplayedCount, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + return await GetImageInternal( + itemId, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + fillWidth, + fillHeight, + blur, + backgroundColor, + foregroundLayer, + item) + .ConfigureAwait(false); + } + + /// <summary> + /// Gets the item's image. + /// </summary> + /// <param name="itemId">Item id.</param> + /// <param name="imageType">Image type.</param> + /// <param name="maxWidth">The maximum image width to return.</param> + /// <param name="maxHeight">The maximum image height to return.</param> + /// <param name="width">The fixed image width to return.</param> + /// <param name="height">The fixed image height to return.</param> + /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> + /// <param name="fillWidth">Width of box to fill.</param> + /// <param name="fillHeight">Height of box to fill.</param> + /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> + /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> + /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> + /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> + /// <param name="blur">Optional. Blur image.</param> + /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> + /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> + /// <param name="imageIndex">Image index.</param> + /// <response code="200">Image stream returned.</response> + /// <response code="404">Item not found.</response> + /// <returns> + /// A <see cref="FileStreamResult"/> containing the file stream on success, + /// or a <see cref="NotFoundResult"/> if item not found. + /// </returns> + [HttpGet("Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}")] + [HttpHead("Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}", Name = "HeadItemImage2")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + public async Task<ActionResult> GetItemImage2( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] ImageType imageType, + [FromRoute, Required] int maxWidth, + [FromRoute, Required] int maxHeight, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, + [FromRoute, Required] string tag, + [FromRoute, Required] ImageFormat format, + [FromRoute, Required] double percentPlayed, + [FromRoute, Required] int unplayedCount, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer, + [FromRoute, Required] int imageIndex) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + return await GetImageInternal( + itemId, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + fillWidth, + fillHeight, + blur, + backgroundColor, + foregroundLayer, + item) + .ConfigureAwait(false); + } + + /// <summary> + /// Get artist image by name. + /// </summary> + /// <param name="name">Artist name.</param> + /// <param name="imageType">Image type.</param> + /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> + /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> + /// <param name="maxWidth">The maximum image width to return.</param> + /// <param name="maxHeight">The maximum image height to return.</param> + /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> + /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> + /// <param name="width">The fixed image width to return.</param> + /// <param name="height">The fixed image height to return.</param> + /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> + /// <param name="fillWidth">Width of box to fill.</param> + /// <param name="fillHeight">Height of box to fill.</param> + /// <param name="blur">Optional. Blur image.</param> + /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> + /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> + /// <param name="imageIndex">Image index.</param> + /// <response code="200">Image stream returned.</response> + /// <response code="404">Item not found.</response> + /// <returns> + /// A <see cref="FileStreamResult"/> containing the file stream on success, + /// or a <see cref="NotFoundResult"/> if item not found. + /// </returns> + [HttpGet("Artists/{name}/Images/{imageType}/{imageIndex}")] + [HttpHead("Artists/{name}/Images/{imageType}/{imageIndex}", Name = "HeadArtistImage")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + public async Task<ActionResult> GetArtistImage( + [FromRoute, Required] string name, + [FromRoute, Required] ImageType imageType, + [FromQuery] string? tag, + [FromQuery] ImageFormat? format, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] double? percentPlayed, + [FromQuery] int? unplayedCount, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer, + [FromRoute, Required] int imageIndex) + { + var item = _libraryManager.GetArtist(name); + if (item is null) + { + return NotFound(); + } + + return await GetImageInternal( + item.Id, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + fillWidth, + fillHeight, + blur, + backgroundColor, + foregroundLayer, + item) + .ConfigureAwait(false); + } + + /// <summary> + /// Get genre image by name. + /// </summary> + /// <param name="name">Genre name.</param> + /// <param name="imageType">Image type.</param> + /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> + /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> + /// <param name="maxWidth">The maximum image width to return.</param> + /// <param name="maxHeight">The maximum image height to return.</param> + /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> + /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> + /// <param name="width">The fixed image width to return.</param> + /// <param name="height">The fixed image height to return.</param> + /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> + /// <param name="fillWidth">Width of box to fill.</param> + /// <param name="fillHeight">Height of box to fill.</param> + /// <param name="blur">Optional. Blur image.</param> + /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> + /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> + /// <param name="imageIndex">Image index.</param> + /// <response code="200">Image stream returned.</response> + /// <response code="404">Item not found.</response> + /// <returns> + /// A <see cref="FileStreamResult"/> containing the file stream on success, + /// or a <see cref="NotFoundResult"/> if item not found. + /// </returns> + [HttpGet("Genres/{name}/Images/{imageType}")] + [HttpHead("Genres/{name}/Images/{imageType}", Name = "HeadGenreImage")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + public async Task<ActionResult> GetGenreImage( + [FromRoute, Required] string name, + [FromRoute, Required] ImageType imageType, + [FromQuery] string? tag, + [FromQuery] ImageFormat? format, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] double? percentPlayed, + [FromQuery] int? unplayedCount, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer, + [FromQuery] int? imageIndex) + { + var item = _libraryManager.GetGenre(name); + if (item is null) + { + return NotFound(); + } + + return await GetImageInternal( + item.Id, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + fillWidth, + fillHeight, + blur, + backgroundColor, + foregroundLayer, + item) + .ConfigureAwait(false); + } + + /// <summary> + /// Get genre image by name. + /// </summary> + /// <param name="name">Genre name.</param> + /// <param name="imageType">Image type.</param> + /// <param name="imageIndex">Image index.</param> + /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> + /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> + /// <param name="maxWidth">The maximum image width to return.</param> + /// <param name="maxHeight">The maximum image height to return.</param> + /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> + /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> + /// <param name="width">The fixed image width to return.</param> + /// <param name="height">The fixed image height to return.</param> + /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> + /// <param name="fillWidth">Width of box to fill.</param> + /// <param name="fillHeight">Height of box to fill.</param> + /// <param name="blur">Optional. Blur image.</param> + /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> + /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> + /// <response code="200">Image stream returned.</response> + /// <response code="404">Item not found.</response> + /// <returns> + /// A <see cref="FileStreamResult"/> containing the file stream on success, + /// or a <see cref="NotFoundResult"/> if item not found. + /// </returns> + [HttpGet("Genres/{name}/Images/{imageType}/{imageIndex}")] + [HttpHead("Genres/{name}/Images/{imageType}/{imageIndex}", Name = "HeadGenreImageByIndex")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + public async Task<ActionResult> GetGenreImageByIndex( + [FromRoute, Required] string name, + [FromRoute, Required] ImageType imageType, + [FromRoute, Required] int imageIndex, + [FromQuery] string? tag, + [FromQuery] ImageFormat? format, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] double? percentPlayed, + [FromQuery] int? unplayedCount, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer) + { + var item = _libraryManager.GetGenre(name); + if (item is null) + { + return NotFound(); + } + + return await GetImageInternal( + item.Id, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + fillWidth, + fillHeight, + blur, + backgroundColor, + foregroundLayer, + item) + .ConfigureAwait(false); + } + + /// <summary> + /// Get music genre image by name. + /// </summary> + /// <param name="name">Music genre name.</param> + /// <param name="imageType">Image type.</param> + /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> + /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> + /// <param name="maxWidth">The maximum image width to return.</param> + /// <param name="maxHeight">The maximum image height to return.</param> + /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> + /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> + /// <param name="width">The fixed image width to return.</param> + /// <param name="height">The fixed image height to return.</param> + /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> + /// <param name="fillWidth">Width of box to fill.</param> + /// <param name="fillHeight">Height of box to fill.</param> + /// <param name="blur">Optional. Blur image.</param> + /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> + /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> + /// <param name="imageIndex">Image index.</param> + /// <response code="200">Image stream returned.</response> + /// <response code="404">Item not found.</response> + /// <returns> + /// A <see cref="FileStreamResult"/> containing the file stream on success, + /// or a <see cref="NotFoundResult"/> if item not found. + /// </returns> + [HttpGet("MusicGenres/{name}/Images/{imageType}")] + [HttpHead("MusicGenres/{name}/Images/{imageType}", Name = "HeadMusicGenreImage")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + public async Task<ActionResult> GetMusicGenreImage( + [FromRoute, Required] string name, + [FromRoute, Required] ImageType imageType, + [FromQuery] string? tag, + [FromQuery] ImageFormat? format, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] double? percentPlayed, + [FromQuery] int? unplayedCount, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer, + [FromQuery] int? imageIndex) + { + var item = _libraryManager.GetMusicGenre(name); + if (item is null) + { + return NotFound(); + } + + return await GetImageInternal( + item.Id, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + fillWidth, + fillHeight, + blur, + backgroundColor, + foregroundLayer, + item) + .ConfigureAwait(false); + } + + /// <summary> + /// Get music genre image by name. + /// </summary> + /// <param name="name">Music genre name.</param> + /// <param name="imageType">Image type.</param> + /// <param name="imageIndex">Image index.</param> + /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> + /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> + /// <param name="maxWidth">The maximum image width to return.</param> + /// <param name="maxHeight">The maximum image height to return.</param> + /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> + /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> + /// <param name="width">The fixed image width to return.</param> + /// <param name="height">The fixed image height to return.</param> + /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> + /// <param name="fillWidth">Width of box to fill.</param> + /// <param name="fillHeight">Height of box to fill.</param> + /// <param name="blur">Optional. Blur image.</param> + /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> + /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> + /// <response code="200">Image stream returned.</response> + /// <response code="404">Item not found.</response> + /// <returns> + /// A <see cref="FileStreamResult"/> containing the file stream on success, + /// or a <see cref="NotFoundResult"/> if item not found. + /// </returns> + [HttpGet("MusicGenres/{name}/Images/{imageType}/{imageIndex}")] + [HttpHead("MusicGenres/{name}/Images/{imageType}/{imageIndex}", Name = "HeadMusicGenreImageByIndex")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + public async Task<ActionResult> GetMusicGenreImageByIndex( + [FromRoute, Required] string name, + [FromRoute, Required] ImageType imageType, + [FromRoute, Required] int imageIndex, + [FromQuery] string? tag, + [FromQuery] ImageFormat? format, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] double? percentPlayed, + [FromQuery] int? unplayedCount, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer) + { + var item = _libraryManager.GetMusicGenre(name); + if (item is null) + { + return NotFound(); + } + + return await GetImageInternal( + item.Id, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + fillWidth, + fillHeight, + blur, + backgroundColor, + foregroundLayer, + item) + .ConfigureAwait(false); + } + + /// <summary> + /// Get person image by name. + /// </summary> + /// <param name="name">Person name.</param> + /// <param name="imageType">Image type.</param> + /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> + /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> + /// <param name="maxWidth">The maximum image width to return.</param> + /// <param name="maxHeight">The maximum image height to return.</param> + /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> + /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> + /// <param name="width">The fixed image width to return.</param> + /// <param name="height">The fixed image height to return.</param> + /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> + /// <param name="fillWidth">Width of box to fill.</param> + /// <param name="fillHeight">Height of box to fill.</param> + /// <param name="blur">Optional. Blur image.</param> + /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> + /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> + /// <param name="imageIndex">Image index.</param> + /// <response code="200">Image stream returned.</response> + /// <response code="404">Item not found.</response> + /// <returns> + /// A <see cref="FileStreamResult"/> containing the file stream on success, + /// or a <see cref="NotFoundResult"/> if item not found. + /// </returns> + [HttpGet("Persons/{name}/Images/{imageType}")] + [HttpHead("Persons/{name}/Images/{imageType}", Name = "HeadPersonImage")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + public async Task<ActionResult> GetPersonImage( + [FromRoute, Required] string name, + [FromRoute, Required] ImageType imageType, + [FromQuery] string? tag, + [FromQuery] ImageFormat? format, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] double? percentPlayed, + [FromQuery] int? unplayedCount, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer, + [FromQuery] int? imageIndex) + { + var item = _libraryManager.GetPerson(name); + if (item is null) + { + return NotFound(); + } + + return await GetImageInternal( + item.Id, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + fillWidth, + fillHeight, + blur, + backgroundColor, + foregroundLayer, + item) + .ConfigureAwait(false); + } + + /// <summary> + /// Get person image by name. + /// </summary> + /// <param name="name">Person name.</param> + /// <param name="imageType">Image type.</param> + /// <param name="imageIndex">Image index.</param> + /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> + /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> + /// <param name="maxWidth">The maximum image width to return.</param> + /// <param name="maxHeight">The maximum image height to return.</param> + /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> + /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> + /// <param name="width">The fixed image width to return.</param> + /// <param name="height">The fixed image height to return.</param> + /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> + /// <param name="fillWidth">Width of box to fill.</param> + /// <param name="fillHeight">Height of box to fill.</param> + /// <param name="blur">Optional. Blur image.</param> + /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> + /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> + /// <response code="200">Image stream returned.</response> + /// <response code="404">Item not found.</response> + /// <returns> + /// A <see cref="FileStreamResult"/> containing the file stream on success, + /// or a <see cref="NotFoundResult"/> if item not found. + /// </returns> + [HttpGet("Persons/{name}/Images/{imageType}/{imageIndex}")] + [HttpHead("Persons/{name}/Images/{imageType}/{imageIndex}", Name = "HeadPersonImageByIndex")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + public async Task<ActionResult> GetPersonImageByIndex( + [FromRoute, Required] string name, + [FromRoute, Required] ImageType imageType, + [FromRoute, Required] int imageIndex, + [FromQuery] string? tag, + [FromQuery] ImageFormat? format, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] double? percentPlayed, + [FromQuery] int? unplayedCount, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer) + { + var item = _libraryManager.GetPerson(name); + if (item is null) + { + return NotFound(); + } + + return await GetImageInternal( + item.Id, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + fillWidth, + fillHeight, + blur, + backgroundColor, + foregroundLayer, + item) + .ConfigureAwait(false); + } + + /// <summary> + /// Get studio image by name. + /// </summary> + /// <param name="name">Studio name.</param> + /// <param name="imageType">Image type.</param> + /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> + /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> + /// <param name="maxWidth">The maximum image width to return.</param> + /// <param name="maxHeight">The maximum image height to return.</param> + /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> + /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> + /// <param name="width">The fixed image width to return.</param> + /// <param name="height">The fixed image height to return.</param> + /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> + /// <param name="fillWidth">Width of box to fill.</param> + /// <param name="fillHeight">Height of box to fill.</param> + /// <param name="blur">Optional. Blur image.</param> + /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> + /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> + /// <param name="imageIndex">Image index.</param> + /// <response code="200">Image stream returned.</response> + /// <response code="404">Item not found.</response> + /// <returns> + /// A <see cref="FileStreamResult"/> containing the file stream on success, + /// or a <see cref="NotFoundResult"/> if item not found. + /// </returns> + [HttpGet("Studios/{name}/Images/{imageType}")] + [HttpHead("Studios/{name}/Images/{imageType}", Name = "HeadStudioImage")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + public async Task<ActionResult> GetStudioImage( + [FromRoute, Required] string name, + [FromRoute, Required] ImageType imageType, + [FromQuery] string? tag, + [FromQuery] ImageFormat? format, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] double? percentPlayed, + [FromQuery] int? unplayedCount, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer, + [FromQuery] int? imageIndex) + { + var item = _libraryManager.GetStudio(name); + if (item is null) + { + return NotFound(); + } + + return await GetImageInternal( + item.Id, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + fillWidth, + fillHeight, + blur, + backgroundColor, + foregroundLayer, + item) + .ConfigureAwait(false); + } + + /// <summary> + /// Get studio image by name. + /// </summary> + /// <param name="name">Studio name.</param> + /// <param name="imageType">Image type.</param> + /// <param name="imageIndex">Image index.</param> + /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> + /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> + /// <param name="maxWidth">The maximum image width to return.</param> + /// <param name="maxHeight">The maximum image height to return.</param> + /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> + /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> + /// <param name="width">The fixed image width to return.</param> + /// <param name="height">The fixed image height to return.</param> + /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> + /// <param name="fillWidth">Width of box to fill.</param> + /// <param name="fillHeight">Height of box to fill.</param> + /// <param name="blur">Optional. Blur image.</param> + /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> + /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> + /// <response code="200">Image stream returned.</response> + /// <response code="404">Item not found.</response> + /// <returns> + /// A <see cref="FileStreamResult"/> containing the file stream on success, + /// or a <see cref="NotFoundResult"/> if item not found. + /// </returns> + [HttpGet("Studios/{name}/Images/{imageType}/{imageIndex}")] + [HttpHead("Studios/{name}/Images/{imageType}/{imageIndex}", Name = "HeadStudioImageByIndex")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + public async Task<ActionResult> GetStudioImageByIndex( + [FromRoute, Required] string name, + [FromRoute, Required] ImageType imageType, + [FromRoute, Required] int imageIndex, + [FromQuery] string? tag, + [FromQuery] ImageFormat? format, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] double? percentPlayed, + [FromQuery] int? unplayedCount, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer) + { + var item = _libraryManager.GetStudio(name); + if (item is null) + { + return NotFound(); + } + + return await GetImageInternal( + item.Id, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + fillWidth, + fillHeight, + blur, + backgroundColor, + foregroundLayer, + item) + .ConfigureAwait(false); + } + + /// <summary> + /// Get user profile image. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> + /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> + /// <response code="200">Image stream returned.</response> + /// <response code="400">User id not provided.</response> + /// <response code="404">Item not found.</response> + /// <returns> + /// A <see cref="FileStreamResult"/> containing the file stream on success, + /// or a <see cref="NotFoundResult"/> if item not found. + /// </returns> + [HttpGet("UserImage")] + [HttpHead("UserImage", Name = "HeadUserImage")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + public async Task<ActionResult> GetUserImage( + [FromQuery] Guid? userId, + [FromQuery] string? tag, + [FromQuery] ImageFormat? format) + { + var requestUserId = userId ?? User.GetUserId(); + if (requestUserId.IsEmpty()) + { + return BadRequest("UserId is required if unauthenticated"); + } + + var user = _userManager.GetUserById(requestUserId); + if (user?.ProfileImage is null) + { + return NotFound(); + } + + var info = new ItemImageInfo + { + Path = user.ProfileImage.Path, + Type = ImageType.Profile, + DateModified = user.ProfileImage.LastModified + }; + + return await GetImageInternal( + user.Id, + ImageType.Profile, + null, + tag, + format, + null, + null, + null, + null, + null, + null, + 90, + null, + null, + null, + null, + null, + null, + info) + .ConfigureAwait(false); + } + + /// <summary> + /// Get user profile image. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="imageType">Image type.</param> + /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> + /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> + /// <param name="maxWidth">The maximum image width to return.</param> + /// <param name="maxHeight">The maximum image height to return.</param> + /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> + /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> + /// <param name="width">The fixed image width to return.</param> + /// <param name="height">The fixed image height to return.</param> + /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> + /// <param name="fillWidth">Width of box to fill.</param> + /// <param name="fillHeight">Height of box to fill.</param> + /// <param name="blur">Optional. Blur image.</param> + /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> + /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> + /// <param name="imageIndex">Image index.</param> + /// <response code="200">Image stream returned.</response> + /// <response code="404">Item not found.</response> + /// <returns> + /// A <see cref="FileStreamResult"/> containing the file stream on success, + /// or a <see cref="NotFoundResult"/> if item not found. + /// </returns> + [HttpGet("Users/{userId}/Images/{imageType}")] + [HttpHead("Users/{userId}/Images/{imageType}", Name = "HeadUserImageLegacy")] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + public Task<ActionResult> GetUserImageLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] ImageType imageType, + [FromQuery] string? tag, + [FromQuery] ImageFormat? format, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] double? percentPlayed, + [FromQuery] int? unplayedCount, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer, + [FromQuery] int? imageIndex) + => GetUserImage( + userId, + tag, + format); + + /// <summary> + /// Get user profile image. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="imageType">Image type.</param> + /// <param name="imageIndex">Image index.</param> + /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> + /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> + /// <param name="maxWidth">The maximum image width to return.</param> + /// <param name="maxHeight">The maximum image height to return.</param> + /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> + /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> + /// <param name="width">The fixed image width to return.</param> + /// <param name="height">The fixed image height to return.</param> + /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> + /// <param name="fillWidth">Width of box to fill.</param> + /// <param name="fillHeight">Height of box to fill.</param> + /// <param name="blur">Optional. Blur image.</param> + /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> + /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> + /// <response code="200">Image stream returned.</response> + /// <response code="404">Item not found.</response> + /// <returns> + /// A <see cref="FileStreamResult"/> containing the file stream on success, + /// or a <see cref="NotFoundResult"/> if item not found. + /// </returns> + [HttpGet("Users/{userId}/Images/{imageType}/{imageIndex}")] + [HttpHead("Users/{userId}/Images/{imageType}/{imageIndex}", Name = "HeadUserImageByIndexLegacy")] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + public Task<ActionResult> GetUserImageByIndexLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] ImageType imageType, + [FromRoute, Required] int imageIndex, + [FromQuery] string? tag, + [FromQuery] ImageFormat? format, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] double? percentPlayed, + [FromQuery] int? unplayedCount, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer) + => GetUserImage( + userId, + tag, + format); + + /// <summary> + /// Generates or gets the splashscreen. + /// </summary> + /// <param name="tag">Supply the cache tag from the item object to receive strong caching headers.</param> + /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> + /// <response code="200">Splashscreen returned successfully.</response> + /// <returns>The splashscreen.</returns> + [HttpGet("Branding/Splashscreen")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesImageFile] + public async Task<ActionResult> GetSplashscreen( + [FromQuery] string? tag, + [FromQuery] ImageFormat? format) + { + var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding"); + var isAdmin = User.IsInRole(Constants.UserRoles.Administrator); + if (!brandingOptions.SplashscreenEnabled && !isAdmin) + { + return NotFound(); + } + + string splashscreenPath; + + if (!string.IsNullOrWhiteSpace(brandingOptions.SplashscreenLocation) + && System.IO.File.Exists(brandingOptions.SplashscreenLocation)) + { + splashscreenPath = brandingOptions.SplashscreenLocation; + } + else + { + splashscreenPath = Path.Combine(_appPaths.DataPath, "splashscreen.png"); + if (!System.IO.File.Exists(splashscreenPath)) + { + return NotFound(); + } + } + + var outputFormats = GetOutputFormats(format); + + TimeSpan? cacheDuration = null; + if (!string.IsNullOrEmpty(tag)) + { + cacheDuration = TimeSpan.FromDays(365); + } + + var options = new ImageProcessingOptions + { + Image = new ItemImageInfo + { + Path = splashscreenPath + }, + Height = null, + MaxHeight = null, + MaxWidth = null, + FillHeight = null, + FillWidth = null, + Quality = 90, + Width = null, + Blur = null, + BackgroundColor = null, + ForegroundLayer = null, + SupportedOutputFormats = outputFormats + }; + + return await GetImageResult( + options, + cacheDuration, + ImmutableDictionary<string, string>.Empty) + .ConfigureAwait(false); + } + + /// <summary> + /// Uploads a custom splashscreen. + /// The body is expected to the image contents base64 encoded. + /// </summary> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + /// <response code="204">Successfully uploaded new splashscreen.</response> + /// <response code="400">Error reading MimeType from uploaded image.</response> + /// <response code="403">User does not have permission to upload splashscreen..</response> + /// <exception cref="ArgumentException">Error reading the image format.</exception> + [HttpPost("Branding/Splashscreen")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [AcceptsImageFile] + public async Task<ActionResult> UploadCustomSplashscreen() + { + if (!TryGetImageExtensionFromContentType(Request.ContentType, out var extension)) + { + return BadRequest("Incorrect ContentType."); + } + + var stream = GetFromBase64Stream(Request.Body); + await using (stream.ConfigureAwait(false)) + { + var filePath = Path.Combine(_appPaths.DataPath, "splashscreen-upload" + extension); + var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding"); + brandingOptions.SplashscreenLocation = filePath; + _serverConfigurationManager.SaveConfiguration("branding", brandingOptions); + + var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); + await using (fs.ConfigureAwait(false)) + { + await stream.CopyToAsync(fs, CancellationToken.None).ConfigureAwait(false); + } + + return NoContent(); + } + } + + /// <summary> + /// Delete a custom splashscreen. + /// </summary> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + /// <response code="204">Successfully deleted the custom splashscreen.</response> + /// <response code="403">User does not have permission to delete splashscreen..</response> + [HttpDelete("Branding/Splashscreen")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult DeleteCustomSplashscreen() + { + var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding"); + if (!string.IsNullOrEmpty(brandingOptions.SplashscreenLocation) + && System.IO.File.Exists(brandingOptions.SplashscreenLocation)) + { + System.IO.File.Delete(brandingOptions.SplashscreenLocation); + brandingOptions.SplashscreenLocation = null; + _serverConfigurationManager.SaveConfiguration("branding", brandingOptions); + } + + return NoContent(); + } + + private ImageInfo? GetImageInfo(BaseItem item, ItemImageInfo info, int? imageIndex) + { + int? width = null; + int? height = null; + string? blurhash = null; + long length = 0; + + try + { + if (info.IsLocalFile) + { + var fileInfo = _fileSystem.GetFileInfo(info.Path); + length = fileInfo.Length; + + blurhash = info.BlurHash; + width = info.Width; + height = info.Height; + + if (width <= 0 || height <= 0) + { + width = null; + height = null; + } + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting image information for {Item}", item.Name); + } + + try + { + return new ImageInfo + { + Path = info.Path, + ImageIndex = imageIndex, + ImageType = info.Type, + ImageTag = _imageProcessor.GetImageCacheTag(item, info), + Size = length, + BlurHash = blurhash, + Width = width, + Height = height + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting image information for {Path}", info.Path); + return null; + } + } + + private async Task<ActionResult> GetImageInternal( + Guid itemId, + ImageType imageType, + int? imageIndex, + string? tag, + ImageFormat? format, + int? maxWidth, + int? maxHeight, + double? percentPlayed, + int? unplayedCount, + int? width, + int? height, + int? quality, + int? fillWidth, + int? fillHeight, + int? blur, + string? backgroundColor, + string? foregroundLayer, + BaseItem? item, + ItemImageInfo? imageInfo = null) + { + if (percentPlayed.HasValue) + { + if (percentPlayed.Value <= 0) + { + percentPlayed = null; + } + else if (percentPlayed.Value >= 100) + { + percentPlayed = null; + } + } + + if (percentPlayed.HasValue) + { + unplayedCount = null; + } + + if (unplayedCount.HasValue + && unplayedCount.Value <= 0) + { + unplayedCount = null; + } + + if (imageInfo is null) + { + imageInfo = item?.GetImageInfo(imageType, imageIndex ?? 0); + if (imageInfo is null) + { + return NotFound(string.Format(NumberFormatInfo.InvariantInfo, "{0} does not have an image of type {1}", item?.Name, imageType)); + } + } + + var outputFormats = GetOutputFormats(format); + + TimeSpan? cacheDuration = null; + + if (!string.IsNullOrEmpty(tag)) + { + cacheDuration = TimeSpan.FromDays(365); + } + + var responseHeaders = new Dictionary<string, string> + { + { "transferMode.dlna.org", "Interactive" }, + { "realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*" } + }; + + if (!imageInfo.IsLocalFile && item is not null) + { + imageInfo = await _libraryManager.ConvertImageToLocal(item, imageInfo, imageIndex ?? 0).ConfigureAwait(false); + } + + var options = new ImageProcessingOptions + { + Height = height, + ImageIndex = imageIndex ?? 0, + Image = imageInfo, + Item = item, + ItemId = itemId, + MaxHeight = maxHeight, + MaxWidth = maxWidth, + FillHeight = fillHeight, + FillWidth = fillWidth, + Quality = quality ?? 100, + Width = width, + PercentPlayed = percentPlayed ?? 0, + UnplayedCount = unplayedCount, + Blur = blur, + BackgroundColor = backgroundColor, + ForegroundLayer = foregroundLayer, + SupportedOutputFormats = outputFormats + }; + + return await GetImageResult( + options, + cacheDuration, + responseHeaders).ConfigureAwait(false); + } + + private ImageFormat[] GetOutputFormats(ImageFormat? format) + { + if (format.HasValue) + { + return [format.Value]; + } + + return GetClientSupportedFormats(); + } + + private ImageFormat[] GetClientSupportedFormats() + { + var supportedFormats = Request.Headers.GetCommaSeparatedValues(HeaderNames.Accept); + for (var i = 0; i < supportedFormats.Length; i++) + { + // Remove charsets etc. (anything after semi-colon) + var type = supportedFormats[i]; + int index = type.IndexOf(';', StringComparison.Ordinal); + if (index != -1) + { + supportedFormats[i] = type.Substring(0, index); + } + } + + var acceptParam = Request.Query[HeaderNames.Accept]; + + var supportsWebP = SupportsFormat(supportedFormats, acceptParam, ImageFormat.Webp, false); + + if (!supportsWebP) + { + var userAgent = Request.Headers[HeaderNames.UserAgent].ToString(); + if (userAgent.Contains("crosswalk", StringComparison.OrdinalIgnoreCase) + && userAgent.Contains("android", StringComparison.OrdinalIgnoreCase)) + { + supportsWebP = true; + } + } + + var formats = new List<ImageFormat>(4); + + if (supportsWebP) + { + formats.Add(ImageFormat.Webp); + } + + formats.Add(ImageFormat.Jpg); + formats.Add(ImageFormat.Png); + + if (SupportsFormat(supportedFormats, acceptParam, ImageFormat.Gif, true)) + { + formats.Add(ImageFormat.Gif); + } + + return formats.ToArray(); + } + + private bool SupportsFormat(IReadOnlyCollection<string> requestAcceptTypes, string? acceptParam, ImageFormat format, bool acceptAll) + { + if (requestAcceptTypes.Contains(format.GetMimeType())) + { + return true; + } + + if (acceptAll && requestAcceptTypes.Contains("*/*")) + { + return true; + } + + // Review if this should be jpeg, jpg or both for ImageFormat.Jpg + var normalized = format.ToString().ToLowerInvariant(); + return string.Equals(acceptParam, normalized, StringComparison.OrdinalIgnoreCase); + } + + private async Task<ActionResult> GetImageResult( + ImageProcessingOptions imageProcessingOptions, + TimeSpan? cacheDuration, + IDictionary<string, string> headers) + { + var (imagePath, imageContentType, dateImageModified) = await _imageProcessor.ProcessImage(imageProcessingOptions).ConfigureAwait(false); + + var disableCaching = Request.Headers[HeaderNames.CacheControl].Contains("no-cache"); + var parsingSuccessful = DateTime.TryParse(Request.Headers[HeaderNames.IfModifiedSince], out var ifModifiedSinceHeader); + + // if the parsing of the IfModifiedSince header was not successful, disable caching + if (!parsingSuccessful) + { + // disableCaching = true; + } + + foreach (var (key, value) in headers) + { + Response.Headers.Append(key, value); + } + + Response.ContentType = imageContentType ?? MediaTypeNames.Text.Plain; + Response.Headers.Append(HeaderNames.Age, Convert.ToInt64((DateTime.UtcNow - dateImageModified).TotalSeconds).ToString(CultureInfo.InvariantCulture)); + Response.Headers.Append(HeaderNames.Vary, HeaderNames.Accept); + + Response.Headers.ContentDisposition = "attachment"; + + if (disableCaching) + { + Response.Headers.Append(HeaderNames.CacheControl, "no-cache, no-store, must-revalidate"); + Response.Headers.Append(HeaderNames.Pragma, "no-cache, no-store, must-revalidate"); + } + else + { + if (cacheDuration.HasValue) + { + Response.Headers.Append(HeaderNames.CacheControl, "public, max-age=" + cacheDuration.Value.TotalSeconds); + } + else + { + Response.Headers.Append(HeaderNames.CacheControl, "public"); + } + + Response.Headers.Append(HeaderNames.LastModified, dateImageModified.ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss \"GMT\"", CultureInfo.InvariantCulture)); + + // if the image was not modified since "ifModifiedSinceHeader"-header, return a HTTP status code 304 not modified + if (!(dateImageModified > ifModifiedSinceHeader) && cacheDuration.HasValue) + { + if (ifModifiedSinceHeader.Add(cacheDuration.Value) < DateTime.UtcNow) + { + Response.StatusCode = StatusCodes.Status304NotModified; + return new ContentResult(); + } + } + } + + return PhysicalFile(imagePath, imageContentType ?? MediaTypeNames.Text.Plain); + } + + internal static bool TryGetImageExtensionFromContentType(string? contentType, [NotNullWhen(true)] out string? extension) + { + extension = null; + if (string.IsNullOrEmpty(contentType)) + { + return false; + } + + if (MediaTypeHeaderValue.TryParse(contentType, out var parsedValue) + && parsedValue.MediaType.HasValue + && MimeTypes.IsImage(parsedValue.MediaType.Value)) + { + extension = MimeTypes.ToExtension(parsedValue.MediaType.Value); + return extension is not null; + } + + return false; + } +} diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs new file mode 100644 index 00000000..30195456 --- /dev/null +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -0,0 +1,403 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The instant mix controller. +/// </summary> +[Route("")] +[Authorize] +public class InstantMixController : BaseJellyfinApiController +{ + private readonly IUserManager _userManager; + private readonly IDtoService _dtoService; + private readonly ILibraryManager _libraryManager; + private readonly IMusicManager _musicManager; + + /// <summary> + /// Initializes a new instance of the <see cref="InstantMixController"/> class. + /// </summary> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> + /// <param name="musicManager">Instance of the <see cref="IMusicManager"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + public InstantMixController( + IUserManager userManager, + IDtoService dtoService, + IMusicManager musicManager, + ILibraryManager libraryManager) + { + _userManager = userManager; + _dtoService = dtoService; + _musicManager = musicManager; + _libraryManager = libraryManager; + } + + /// <summary> + /// Creates an instant playlist based on a given song. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <response code="200">Instant playlist returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> + [HttpGet("Songs/{itemId}/InstantMix")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromSong( + [FromRoute, Required] Guid itemId, + [FromQuery] Guid? userId, + [FromQuery] int? limit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] bool? enableImages, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var item = _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); + return GetResult(items, user, limit, dtoOptions); + } + + /// <summary> + /// Creates an instant playlist based on a given album. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <response code="200">Instant playlist returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> + [HttpGet("Albums/{itemId}/InstantMix")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromAlbum( + [FromRoute, Required] Guid itemId, + [FromQuery] Guid? userId, + [FromQuery] int? limit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] bool? enableImages, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var item = _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); + return GetResult(items, user, limit, dtoOptions); + } + + /// <summary> + /// Creates an instant playlist based on a given playlist. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <response code="200">Instant playlist returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> + [HttpGet("Playlists/{itemId}/InstantMix")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromPlaylist( + [FromRoute, Required] Guid itemId, + [FromQuery] Guid? userId, + [FromQuery] int? limit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] bool? enableImages, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var item = _libraryManager.GetItemById<Playlist>(itemId, user); + if (item is null) + { + return NotFound(); + } + + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); + return GetResult(items, user, limit, dtoOptions); + } + + /// <summary> + /// Creates an instant playlist based on a given genre. + /// </summary> + /// <param name="name">The genre name.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <response code="200">Instant playlist returned.</response> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> + [HttpGet("MusicGenres/{name}/InstantMix")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreByName( + [FromRoute, Required] string name, + [FromQuery] Guid? userId, + [FromQuery] int? limit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] bool? enableImages, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + var items = _musicManager.GetInstantMixFromGenres(new[] { name }, user, dtoOptions); + return GetResult(items, user, limit, dtoOptions); + } + + /// <summary> + /// Creates an instant playlist based on a given artist. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <response code="200">Instant playlist returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> + [HttpGet("Artists/{itemId}/InstantMix")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists( + [FromRoute, Required] Guid itemId, + [FromQuery] Guid? userId, + [FromQuery] int? limit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] bool? enableImages, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var item = _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); + return GetResult(items, user, limit, dtoOptions); + } + + /// <summary> + /// Creates an instant playlist based on a given item. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <response code="200">Instant playlist returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> + [HttpGet("Items/{itemId}/InstantMix")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromItem( + [FromRoute, Required] Guid itemId, + [FromQuery] Guid? userId, + [FromQuery] int? limit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] bool? enableImages, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var item = _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); + return GetResult(items, user, limit, dtoOptions); + } + + /// <summary> + /// Creates an instant playlist based on a given artist. + /// </summary> + /// <param name="id">The item id.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <response code="200">Instant playlist returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> + [HttpGet("Artists/InstantMix")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Obsolete("Use GetInstantMixFromArtists")] + public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists2( + [FromQuery, Required] Guid id, + [FromQuery] Guid? userId, + [FromQuery] int? limit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] bool? enableImages, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes) + { + return GetInstantMixFromArtists( + id, + userId, + limit, + fields, + enableImages, + enableUserData, + imageTypeLimit, + enableImageTypes); + } + + /// <summary> + /// Creates an instant playlist based on a given genre. + /// </summary> + /// <param name="id">The item id.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <response code="200">Instant playlist returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> + [HttpGet("MusicGenres/InstantMix")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreById( + [FromQuery, Required] Guid id, + [FromQuery] Guid? userId, + [FromQuery] int? limit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] bool? enableImages, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var item = _libraryManager.GetItemById<BaseItem>(id, user); + if (item is null) + { + return NotFound(); + } + + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); + return GetResult(items, user, limit, dtoOptions); + } + + private QueryResult<BaseItemDto> GetResult(IReadOnlyList<BaseItem> items, User? user, int? limit, DtoOptions dtoOptions) + { + var totalCount = items.Count; + + if (limit.HasValue && limit < items.Count) + { + items = items.Take(limit.Value).ToArray(); + } + + var result = new QueryResult<BaseItemDto>( + 0, + totalCount, + _dtoService.GetBaseItemDtos(items, dtoOptions, user)); + + return result; + } +} diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs new file mode 100644 index 00000000..d009f80a --- /dev/null +++ b/Jellyfin.Api/Controllers/ItemLookupController.cs @@ -0,0 +1,282 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using MediaBrowser.Common.Api; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Providers; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Item lookup controller. +/// </summary> +[Route("")] +[Authorize] +public class ItemLookupController : BaseJellyfinApiController +{ + private readonly IProviderManager _providerManager; + private readonly IFileSystem _fileSystem; + private readonly ILibraryManager _libraryManager; + private readonly ILogger<ItemLookupController> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="ItemLookupController"/> class. + /// </summary> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="logger">Instance of the <see cref="ILogger{ItemLookupController}"/> interface.</param> + public ItemLookupController( + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + ILogger<ItemLookupController> logger) + { + _providerManager = providerManager; + _fileSystem = fileSystem; + _libraryManager = libraryManager; + _logger = logger; + } + + /// <summary> + /// Get the item's external id info. + /// </summary> + /// <param name="itemId">Item id.</param> + /// <response code="200">External id info retrieved.</response> + /// <response code="404">Item not found.</response> + /// <returns>List of external id info.</returns> + [HttpGet("Items/{itemId}/ExternalIdInfos")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<IEnumerable<ExternalIdInfo>> GetExternalIdInfos([FromRoute, Required] Guid itemId) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + return Ok(_providerManager.GetExternalIdInfos(item)); + } + + /// <summary> + /// Get movie remote search. + /// </summary> + /// <param name="query">Remote search query.</param> + /// <response code="200">Movie remote search executed.</response> + /// <returns> + /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. + /// The task result contains an <see cref="OkResult"/> containing the list of remote search results. + /// </returns> + [HttpPost("Items/RemoteSearch/Movie")] + public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMovieRemoteSearchResults([FromBody, Required] RemoteSearchQuery<MovieInfo> query) + { + var results = await _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(query, CancellationToken.None) + .ConfigureAwait(false); + return Ok(results); + } + + /// <summary> + /// Get trailer remote search. + /// </summary> + /// <param name="query">Remote search query.</param> + /// <response code="200">Trailer remote search executed.</response> + /// <returns> + /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. + /// The task result contains an <see cref="OkResult"/> containing the list of remote search results. + /// </returns> + [HttpPost("Items/RemoteSearch/Trailer")] + public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetTrailerRemoteSearchResults([FromBody, Required] RemoteSearchQuery<TrailerInfo> query) + { + var results = await _providerManager.GetRemoteSearchResults<Trailer, TrailerInfo>(query, CancellationToken.None) + .ConfigureAwait(false); + return Ok(results); + } + + /// <summary> + /// Get music video remote search. + /// </summary> + /// <param name="query">Remote search query.</param> + /// <response code="200">Music video remote search executed.</response> + /// <returns> + /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. + /// The task result contains an <see cref="OkResult"/> containing the list of remote search results. + /// </returns> + [HttpPost("Items/RemoteSearch/MusicVideo")] + public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicVideoRemoteSearchResults([FromBody, Required] RemoteSearchQuery<MusicVideoInfo> query) + { + var results = await _providerManager.GetRemoteSearchResults<MusicVideo, MusicVideoInfo>(query, CancellationToken.None) + .ConfigureAwait(false); + return Ok(results); + } + + /// <summary> + /// Get series remote search. + /// </summary> + /// <param name="query">Remote search query.</param> + /// <response code="200">Series remote search executed.</response> + /// <returns> + /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. + /// The task result contains an <see cref="OkResult"/> containing the list of remote search results. + /// </returns> + [HttpPost("Items/RemoteSearch/Series")] + public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetSeriesRemoteSearchResults([FromBody, Required] RemoteSearchQuery<SeriesInfo> query) + { + var results = await _providerManager.GetRemoteSearchResults<Series, SeriesInfo>(query, CancellationToken.None) + .ConfigureAwait(false); + return Ok(results); + } + + /// <summary> + /// Get box set remote search. + /// </summary> + /// <param name="query">Remote search query.</param> + /// <response code="200">Box set remote search executed.</response> + /// <returns> + /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. + /// The task result contains an <see cref="OkResult"/> containing the list of remote search results. + /// </returns> + [HttpPost("Items/RemoteSearch/BoxSet")] + public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetBoxSetRemoteSearchResults([FromBody, Required] RemoteSearchQuery<BoxSetInfo> query) + { + var results = await _providerManager.GetRemoteSearchResults<BoxSet, BoxSetInfo>(query, CancellationToken.None) + .ConfigureAwait(false); + return Ok(results); + } + + /// <summary> + /// Get music artist remote search. + /// </summary> + /// <param name="query">Remote search query.</param> + /// <response code="200">Music artist remote search executed.</response> + /// <returns> + /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. + /// The task result contains an <see cref="OkResult"/> containing the list of remote search results. + /// </returns> + [HttpPost("Items/RemoteSearch/MusicArtist")] + public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicArtistRemoteSearchResults([FromBody, Required] RemoteSearchQuery<ArtistInfo> query) + { + var results = await _providerManager.GetRemoteSearchResults<MusicArtist, ArtistInfo>(query, CancellationToken.None) + .ConfigureAwait(false); + return Ok(results); + } + + /// <summary> + /// Get music album remote search. + /// </summary> + /// <param name="query">Remote search query.</param> + /// <response code="200">Music album remote search executed.</response> + /// <returns> + /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. + /// The task result contains an <see cref="OkResult"/> containing the list of remote search results. + /// </returns> + [HttpPost("Items/RemoteSearch/MusicAlbum")] + public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicAlbumRemoteSearchResults([FromBody, Required] RemoteSearchQuery<AlbumInfo> query) + { + var results = await _providerManager.GetRemoteSearchResults<MusicAlbum, AlbumInfo>(query, CancellationToken.None) + .ConfigureAwait(false); + return Ok(results); + } + + /// <summary> + /// Get person remote search. + /// </summary> + /// <param name="query">Remote search query.</param> + /// <response code="200">Person remote search executed.</response> + /// <returns> + /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. + /// The task result contains an <see cref="OkResult"/> containing the list of remote search results. + /// </returns> + [HttpPost("Items/RemoteSearch/Person")] + [Authorize(Policy = Policies.RequiresElevation)] + public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetPersonRemoteSearchResults([FromBody, Required] RemoteSearchQuery<PersonLookupInfo> query) + { + var results = await _providerManager.GetRemoteSearchResults<Person, PersonLookupInfo>(query, CancellationToken.None) + .ConfigureAwait(false); + return Ok(results); + } + + /// <summary> + /// Get book remote search. + /// </summary> + /// <param name="query">Remote search query.</param> + /// <response code="200">Book remote search executed.</response> + /// <returns> + /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. + /// The task result contains an <see cref="OkResult"/> containing the list of remote search results. + /// </returns> + [HttpPost("Items/RemoteSearch/Book")] + public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetBookRemoteSearchResults([FromBody, Required] RemoteSearchQuery<BookInfo> query) + { + var results = await _providerManager.GetRemoteSearchResults<Book, BookInfo>(query, CancellationToken.None) + .ConfigureAwait(false); + return Ok(results); + } + + /// <summary> + /// Applies search criteria to an item and refreshes metadata. + /// </summary> + /// <param name="itemId">Item id.</param> + /// <param name="searchResult">The remote search result.</param> + /// <param name="replaceAllImages">Optional. Whether or not to replace all images. Default: True.</param> + /// <response code="204">Item metadata refreshed.</response> + /// <response code="404">Item not found.</response> + /// <returns> + /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. + /// The task result contains an <see cref="NoContentResult"/>. + /// </returns> + [HttpPost("Items/RemoteSearch/Apply/{itemId}")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> ApplySearchCriteria( + [FromRoute, Required] Guid itemId, + [FromBody, Required] RemoteSearchResult searchResult, + [FromQuery] bool replaceAllImages = true) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + _logger.LogInformation( + "Setting provider id's to item {ItemId}-{ItemName}: {@ProviderIds}", + item.Id, + item.Name, + searchResult.ProviderIds); + + // Since the refresh process won't erase provider Ids, we need to set this explicitly now. + item.ProviderIds = searchResult.ProviderIds; + await _providerManager.RefreshFullItem( + item, + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + MetadataRefreshMode = MetadataRefreshMode.FullRefresh, + ImageRefreshMode = MetadataRefreshMode.FullRefresh, + ReplaceAllMetadata = true, + ReplaceAllImages = replaceAllImages, + SearchResult = searchResult, + RemoveOldMetadata = true + }, + CancellationToken.None).ConfigureAwait(false); + + return NoContent(); + } +} diff --git a/Jellyfin.Api/Controllers/ItemRefreshController.cs b/Jellyfin.Api/Controllers/ItemRefreshController.cs new file mode 100644 index 00000000..7effe61e --- /dev/null +++ b/Jellyfin.Api/Controllers/ItemRefreshController.cs @@ -0,0 +1,93 @@ +using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Jellyfin.Api.Constants; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using MediaBrowser.Common.Api; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.IO; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Item Refresh Controller. +/// </summary> +[Route("Items")] +[Authorize(Policy = Policies.RequiresElevation)] +public class ItemRefreshController : BaseJellyfinApiController +{ + private readonly ILibraryManager _libraryManager; + private readonly IProviderManager _providerManager; + private readonly IFileSystem _fileSystem; + + /// <summary> + /// Initializes a new instance of the <see cref="ItemRefreshController"/> class. + /// </summary> + /// <param name="libraryManager">Instance of <see cref="ILibraryManager"/> interface.</param> + /// <param name="providerManager">Instance of <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of <see cref="IFileSystem"/> interface.</param> + public ItemRefreshController( + ILibraryManager libraryManager, + IProviderManager providerManager, + IFileSystem fileSystem) + { + _libraryManager = libraryManager; + _providerManager = providerManager; + _fileSystem = fileSystem; + } + + /// <summary> + /// Refreshes metadata for an item. + /// </summary> + /// <param name="itemId">Item id.</param> + /// <param name="metadataRefreshMode">(Optional) Specifies the metadata refresh mode.</param> + /// <param name="imageRefreshMode">(Optional) Specifies the image refresh mode.</param> + /// <param name="replaceAllMetadata">(Optional) Determines if metadata should be replaced. Only applicable if mode is FullRefresh.</param> + /// <param name="replaceAllImages">(Optional) Determines if images should be replaced. Only applicable if mode is FullRefresh.</param> + /// <param name="regenerateTrickplay">(Optional) Determines if trickplay images should be replaced. Only applicable if mode is FullRefresh.</param> + /// <response code="204">Item metadata refresh queued.</response> + /// <response code="404">Item to refresh not found.</response> + /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the item could not be found.</returns> + [HttpPost("{itemId}/Refresh")] + [Description("Refreshes metadata for an item.")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult RefreshItem( + [FromRoute, Required] Guid itemId, + [FromQuery] MetadataRefreshMode metadataRefreshMode = MetadataRefreshMode.None, + [FromQuery] MetadataRefreshMode imageRefreshMode = MetadataRefreshMode.None, + [FromQuery] bool replaceAllMetadata = false, + [FromQuery] bool replaceAllImages = false, + [FromQuery] bool regenerateTrickplay = false) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + var refreshOptions = new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + MetadataRefreshMode = metadataRefreshMode, + ImageRefreshMode = imageRefreshMode, + ReplaceAllImages = replaceAllImages, + ReplaceAllMetadata = replaceAllMetadata, + ForceSave = metadataRefreshMode == MetadataRefreshMode.FullRefresh + || imageRefreshMode == MetadataRefreshMode.FullRefresh + || replaceAllImages + || replaceAllMetadata, + IsAutomated = false, + RemoveOldMetadata = replaceAllMetadata, + RegenerateTrickplay = regenerateTrickplay + }; + + _providerManager.QueueRefresh(item.Id, refreshOptions, RefreshPriority.High); + return NoContent(); + } +} diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs new file mode 100644 index 00000000..605d2aee --- /dev/null +++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs @@ -0,0 +1,544 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Data.Enums; +using MediaBrowser.Common.Api; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Item update controller. +/// </summary> +[Route("")] +[Authorize(Policy = Policies.RequiresElevation)] +public class ItemUpdateController : BaseJellyfinApiController +{ + private readonly ILibraryManager _libraryManager; + private readonly IProviderManager _providerManager; + private readonly ILocalizationManager _localizationManager; + private readonly IFileSystem _fileSystem; + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="ItemUpdateController"/> class. + /// </summary> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="localizationManager">Instance of the <see cref="ILocalizationManager"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + public ItemUpdateController( + IFileSystem fileSystem, + ILibraryManager libraryManager, + IProviderManager providerManager, + ILocalizationManager localizationManager, + IServerConfigurationManager serverConfigurationManager) + { + _libraryManager = libraryManager; + _providerManager = providerManager; + _localizationManager = localizationManager; + _fileSystem = fileSystem; + _serverConfigurationManager = serverConfigurationManager; + } + + /// <summary> + /// Updates an item. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="request">The new item properties.</param> + /// <response code="204">Item updated.</response> + /// <response code="404">Item not found.</response> + /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the item could not be found.</returns> + [HttpPost("Items/{itemId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> UpdateItem([FromRoute, Required] Guid itemId, [FromBody, Required] BaseItemDto request) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + var newLockData = request.LockData ?? false; + var isLockedChanged = item.IsLocked != newLockData; + + var series = item as Series; + var displayOrderChanged = series is not null && !string.Equals( + series.DisplayOrder ?? string.Empty, + request.DisplayOrder ?? string.Empty, + StringComparison.OrdinalIgnoreCase); + + // Do this first so that metadata savers can pull the updates from the database. + if (request.People is not null) + { + _libraryManager.UpdatePeople( + item, + request.People.Select(x => new PersonInfo + { + Name = x.Name, + Role = x.Role, + Type = x.Type + }).ToList()); + } + + await UpdateItem(request, item).ConfigureAwait(false); + + item.OnMetadataChanged(); + + await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + + if (isLockedChanged && item.IsFolder) + { + var folder = (Folder)item; + + foreach (var child in folder.GetRecursiveChildren()) + { + child.IsLocked = newLockData; + await child.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + } + } + + if (displayOrderChanged) + { + _providerManager.QueueRefresh( + series!.Id, + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + MetadataRefreshMode = MetadataRefreshMode.FullRefresh, + ImageRefreshMode = MetadataRefreshMode.FullRefresh, + ReplaceAllMetadata = true + }, + RefreshPriority.High); + } + + return NoContent(); + } + + /// <summary> + /// Gets metadata editor info for an item. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <response code="200">Item metadata editor returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>An <see cref="OkResult"/> on success containing the metadata editor, or a <see cref="NotFoundResult"/> if the item could not be found.</returns> + [HttpGet("Items/{itemId}/MetadataEditor")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<MetadataEditorInfo> GetMetadataEditorInfo([FromRoute, Required] Guid itemId) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + var info = new MetadataEditorInfo + { + ParentalRatingOptions = _localizationManager.GetParentalRatings().ToList(), + ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToArray(), + Countries = _localizationManager.GetCountries().ToArray(), + Cultures = _localizationManager.GetCultures() + .DistinctBy(c => c.DisplayName, StringComparer.OrdinalIgnoreCase) + .OrderBy(c => c.DisplayName) + .ToArray() + }; + + if (!item.IsVirtualItem + && item is not ICollectionFolder + && item is not UserView + && item is not AggregateFolder + && item is not LiveTvChannel + && item is not IItemByName + && item.SourceType == SourceType.Library) + { + var inheritedContentType = _libraryManager.GetInheritedContentType(item); + var configuredContentType = _libraryManager.GetConfiguredContentType(item); + + if (inheritedContentType is null || configuredContentType is not null) + { + info.ContentTypeOptions = GetContentTypeOptions(true).ToArray(); + info.ContentType = configuredContentType; + + if (inheritedContentType is null + || inheritedContentType == CollectionType.tvshows + || inheritedContentType == CollectionType.movies) + { + info.ContentTypeOptions = info.ContentTypeOptions + .Where(i => string.IsNullOrWhiteSpace(i.Value) + || string.Equals(i.Value, "TvShows", StringComparison.OrdinalIgnoreCase) + || string.Equals(i.Value, "Movies", StringComparison.OrdinalIgnoreCase)) + .ToArray(); + } + } + } + + return info; + } + + /// <summary> + /// Updates an item's content type. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="contentType">The content type of the item.</param> + /// <response code="204">Item content type updated.</response> + /// <response code="404">Item not found.</response> + /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the item could not be found.</returns> + [HttpPost("Items/{itemId}/ContentType")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult UpdateItemContentType([FromRoute, Required] Guid itemId, [FromQuery] string? contentType) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + var path = item.ContainingFolderPath; + + var types = _serverConfigurationManager.Configuration.ContentTypes + .Where(i => !string.IsNullOrWhiteSpace(i.Name)) + .Where(i => !string.Equals(i.Name, path, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + if (!string.IsNullOrWhiteSpace(contentType)) + { + types.Add(new NameValuePair + { + Name = path, + Value = contentType + }); + } + + _serverConfigurationManager.Configuration.ContentTypes = types.ToArray(); + _serverConfigurationManager.SaveConfiguration(); + return NoContent(); + } + + private async Task UpdateItem(BaseItemDto request, BaseItem item) + { + item.Name = request.Name; + item.ForcedSortName = request.ForcedSortName; + + item.OriginalTitle = string.IsNullOrWhiteSpace(request.OriginalTitle) ? null : request.OriginalTitle; + + item.CriticRating = request.CriticRating; + + item.CommunityRating = request.CommunityRating; + item.IndexNumber = request.IndexNumber; + item.ParentIndexNumber = request.ParentIndexNumber; + item.Overview = request.Overview; + item.Genres = request.Genres; + + if (item is Episode episode) + { + episode.AirsAfterSeasonNumber = request.AirsAfterSeasonNumber; + episode.AirsBeforeEpisodeNumber = request.AirsBeforeEpisodeNumber; + episode.AirsBeforeSeasonNumber = request.AirsBeforeSeasonNumber; + } + + if (request.Height is not null && item is LiveTvChannel channel) + { + channel.Height = request.Height.Value; + } + + if (request.Taglines is not null) + { + item.Tagline = request.Taglines.FirstOrDefault(); + } + + if (request.Studios is not null) + { + item.Studios = Array.ConvertAll(request.Studios, x => x.Name); + } + + if (request.DateCreated.HasValue) + { + item.DateCreated = NormalizeDateTime(request.DateCreated.Value); + } + + item.EndDate = request.EndDate.HasValue ? NormalizeDateTime(request.EndDate.Value) : null; + item.PremiereDate = request.PremiereDate.HasValue ? NormalizeDateTime(request.PremiereDate.Value) : null; + item.ProductionYear = request.ProductionYear; + + request.OfficialRating = string.IsNullOrWhiteSpace(request.OfficialRating) ? null : request.OfficialRating; + item.OfficialRating = request.OfficialRating; + item.CustomRating = request.CustomRating; + + var currentTags = item.Tags; + var newTags = request.Tags; + var removedTags = currentTags.Except(newTags).ToList(); + var addedTags = newTags.Except(currentTags).ToList(); + item.Tags = newTags; + + if (item is Series rseries) + { + foreach (var season in rseries.Children.OfType<Season>()) + { + if (!season.LockedFields.Contains(MetadataField.OfficialRating)) + { + season.OfficialRating = request.OfficialRating; + } + + season.CustomRating = request.CustomRating; + + if (!season.LockedFields.Contains(MetadataField.Tags)) + { + season.Tags = season.Tags.Concat(addedTags).Except(removedTags).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); + } + + season.OnMetadataChanged(); + await season.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + + foreach (var ep in season.Children.OfType<Episode>()) + { + if (!ep.LockedFields.Contains(MetadataField.OfficialRating)) + { + ep.OfficialRating = request.OfficialRating; + } + + ep.CustomRating = request.CustomRating; + + if (!ep.LockedFields.Contains(MetadataField.Tags)) + { + ep.Tags = ep.Tags.Concat(addedTags).Except(removedTags).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); + } + + ep.OnMetadataChanged(); + await ep.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + } + } + } + else if (item is Season season) + { + foreach (var ep in season.Children.OfType<Episode>()) + { + if (!ep.LockedFields.Contains(MetadataField.OfficialRating)) + { + ep.OfficialRating = request.OfficialRating; + } + + ep.CustomRating = request.CustomRating; + + if (!ep.LockedFields.Contains(MetadataField.Tags)) + { + ep.Tags = ep.Tags.Concat(addedTags).Except(removedTags).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); + } + + ep.OnMetadataChanged(); + await ep.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + } + } + else if (item is MusicAlbum album) + { + foreach (BaseItem track in album.Children) + { + if (!track.LockedFields.Contains(MetadataField.OfficialRating)) + { + track.OfficialRating = request.OfficialRating; + } + + track.CustomRating = request.CustomRating; + + if (!track.LockedFields.Contains(MetadataField.Tags)) + { + track.Tags = track.Tags.Concat(addedTags).Except(removedTags).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); + } + + track.OnMetadataChanged(); + await track.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + } + } + + if (request.ProductionLocations is not null) + { + item.ProductionLocations = request.ProductionLocations; + } + + item.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode; + item.PreferredMetadataLanguage = request.PreferredMetadataLanguage; + + if (item is IHasDisplayOrder hasDisplayOrder) + { + hasDisplayOrder.DisplayOrder = request.DisplayOrder; + } + + if (item is IHasAspectRatio hasAspectRatio) + { + hasAspectRatio.AspectRatio = request.AspectRatio; + } + + item.IsLocked = request.LockData ?? false; + + if (request.LockedFields is not null) + { + item.LockedFields = request.LockedFields; + } + + // Only allow this for series. Runtimes for media comes from ffprobe. + if (item is Series) + { + item.RunTimeTicks = request.RunTimeTicks; + } + + foreach (var pair in request.ProviderIds.ToList()) + { + if (string.IsNullOrEmpty(pair.Value)) + { + request.ProviderIds.Remove(pair.Key); + } + } + + item.ProviderIds = request.ProviderIds; + + if (item is Video video) + { + video.Video3DFormat = request.Video3DFormat; + } + + if (request.AlbumArtists is not null) + { + if (item is IHasAlbumArtist hasAlbumArtists) + { + hasAlbumArtists.AlbumArtists = Array.ConvertAll(request.AlbumArtists, i => i.Name.Trim()); + } + } + + if (request.ArtistItems is not null) + { + if (item is IHasArtist hasArtists) + { + hasArtists.Artists = Array.ConvertAll(request.ArtistItems, i => i.Name.Trim()); + } + } + + switch (item) + { + case Audio song: + song.Album = request.Album; + break; + case MusicVideo musicVideo: + musicVideo.Album = request.Album; + break; + case Series series: + { + series.Status = GetSeriesStatus(request); + + if (request.AirDays is not null) + { + series.AirDays = request.AirDays; + series.AirTime = request.AirTime; + } + + break; + } + } + } + + private SeriesStatus? GetSeriesStatus(BaseItemDto item) + { + if (string.IsNullOrEmpty(item.Status)) + { + return null; + } + + return Enum.Parse<SeriesStatus>(item.Status, true); + } + + private DateTime NormalizeDateTime(DateTime val) + { + return DateTime.SpecifyKind(val, DateTimeKind.Utc); + } + + private List<NameValuePair> GetContentTypeOptions(bool isForItem) + { + var list = new List<NameValuePair>(); + + if (isForItem) + { + list.Add(new NameValuePair + { + Name = "Inherit", + Value = string.Empty + }); + } + + list.Add(new NameValuePair + { + Name = "Movies", + Value = "movies" + }); + list.Add(new NameValuePair + { + Name = "Music", + Value = "music" + }); + list.Add(new NameValuePair + { + Name = "Shows", + Value = "tvshows" + }); + + if (!isForItem) + { + list.Add(new NameValuePair + { + Name = "Books", + Value = "books" + }); + } + + list.Add(new NameValuePair + { + Name = "HomeVideos", + Value = "homevideos" + }); + list.Add(new NameValuePair + { + Name = "MusicVideos", + Value = "musicvideos" + }); + list.Add(new NameValuePair + { + Name = "Photos", + Value = "photos" + }); + + if (!isForItem) + { + list.Add(new NameValuePair + { + Name = "MixedContent", + Value = string.Empty + }); + } + + foreach (var val in list) + { + val.Name = _localizationManager.GetLocalizedString(val.Name); + } + + return list; + } +} diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs new file mode 100644 index 00000000..9674ecd0 --- /dev/null +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -0,0 +1,1072 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Data; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The items controller. +/// </summary> +[Route("")] +[Authorize] +public class ItemsController : BaseJellyfinApiController +{ + private readonly IUserManager _userManager; + private readonly ILibraryManager _libraryManager; + private readonly ILocalizationManager _localization; + private readonly IDtoService _dtoService; + private readonly ILogger<ItemsController> _logger; + private readonly ISessionManager _sessionManager; + private readonly IUserDataManager _userDataRepository; + + /// <summary> + /// Initializes a new instance of the <see cref="ItemsController"/> class. + /// </summary> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> + /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param> + /// <param name="userDataRepository">Instance of the <see cref="IUserDataManager"/> interface.</param> + public ItemsController( + IUserManager userManager, + ILibraryManager libraryManager, + ILocalizationManager localization, + IDtoService dtoService, + ILogger<ItemsController> logger, + ISessionManager sessionManager, + IUserDataManager userDataRepository) + { + _userManager = userManager; + _libraryManager = libraryManager; + _localization = localization; + _dtoService = dtoService; + _logger = logger; + _sessionManager = sessionManager; + _userDataRepository = userDataRepository; + } + + /// <summary> + /// Gets items based on a query. + /// </summary> + /// <param name="userId">The user id supplied as query parameter; this is required when not using an API key.</param> + /// <param name="maxOfficialRating">Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).</param> + /// <param name="hasThemeSong">Optional filter by items with theme songs.</param> + /// <param name="hasThemeVideo">Optional filter by items with theme videos.</param> + /// <param name="hasSubtitles">Optional filter by items with subtitles.</param> + /// <param name="hasSpecialFeature">Optional filter by items with special features.</param> + /// <param name="hasTrailer">Optional filter by items with trailers.</param> + /// <param name="adjacentTo">Optional. Return items that are siblings of a supplied item.</param> + /// <param name="indexNumber">Optional filter by index number.</param> + /// <param name="parentIndexNumber">Optional filter by parent index number.</param> + /// <param name="hasParentalRating">Optional filter by items that have or do not have a parental rating.</param> + /// <param name="isHd">Optional filter by items that are HD or not.</param> + /// <param name="is4K">Optional filter by items that are 4K or not.</param> + /// <param name="locationTypes">Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimited.</param> + /// <param name="excludeLocationTypes">Optional. If specified, results will be filtered based on the LocationType. This allows multiple, comma delimited.</param> + /// <param name="isMissing">Optional filter by items that are missing episodes or not.</param> + /// <param name="isUnaired">Optional filter by items that are unaired episodes or not.</param> + /// <param name="minCommunityRating">Optional filter by minimum community rating.</param> + /// <param name="minCriticRating">Optional filter by minimum critic rating.</param> + /// <param name="minPremiereDate">Optional. The minimum premiere date. Format = ISO.</param> + /// <param name="minDateLastSaved">Optional. The minimum last saved date. Format = ISO.</param> + /// <param name="minDateLastSavedForUser">Optional. The minimum last saved date for the current user. Format = ISO.</param> + /// <param name="maxPremiereDate">Optional. The maximum premiere date. Format = ISO.</param> + /// <param name="hasOverview">Optional filter by items that have an overview or not.</param> + /// <param name="hasImdbId">Optional filter by items that have an IMDb id or not.</param> + /// <param name="hasTmdbId">Optional filter by items that have a TMDb id or not.</param> + /// <param name="hasTvdbId">Optional filter by items that have a TVDb id or not.</param> + /// <param name="isMovie">Optional filter for live tv movies.</param> + /// <param name="isSeries">Optional filter for live tv series.</param> + /// <param name="isNews">Optional filter for live tv news.</param> + /// <param name="isKids">Optional filter for live tv kids.</param> + /// <param name="isSports">Optional filter for live tv sports.</param> + /// <param name="excludeItemIds">Optional. If specified, results will be filtered by excluding item ids. This allows multiple, comma delimited.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="recursive">When searching within folders, this determines whether or not the search will be recursive. true/false.</param> + /// <param name="searchTerm">Optional. Filter based on a search term.</param> + /// <param name="sortOrder">Sort Order - Ascending, Descending.</param> + /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param> + /// <param name="excludeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param> + /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimited.</param> + /// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</param> + /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param> + /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param> + /// <param name="imageTypes">Optional. If specified, results will be filtered based on those containing image types. This allows multiple, comma delimited.</param> + /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param> + /// <param name="isPlayed">Optional filter by items that are played, or not.</param> + /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param> + /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.</param> + /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.</param> + /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.</param> + /// <param name="enableUserData">Optional, include user data.</param> + /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="person">Optional. If specified, results will be filtered to include only those containing the specified person.</param> + /// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the specified person id.</param> + /// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.</param> + /// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.</param> + /// <param name="artists">Optional. If specified, results will be filtered based on artists. This allows multiple, pipe delimited.</param> + /// <param name="excludeArtistIds">Optional. If specified, results will be filtered based on artist id. This allows multiple, pipe delimited.</param> + /// <param name="artistIds">Optional. If specified, results will be filtered to include only those containing the specified artist id.</param> + /// <param name="albumArtistIds">Optional. If specified, results will be filtered to include only those containing the specified album artist id.</param> + /// <param name="contributingArtistIds">Optional. If specified, results will be filtered to include only those containing the specified contributing artist id.</param> + /// <param name="albums">Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimited.</param> + /// <param name="albumIds">Optional. If specified, results will be filtered based on album id. This allows multiple, pipe delimited.</param> + /// <param name="ids">Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited.</param> + /// <param name="videoTypes">Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimited.</param> + /// <param name="minOfficialRating">Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).</param> + /// <param name="isLocked">Optional filter by items that are locked.</param> + /// <param name="isPlaceHolder">Optional filter by items that are placeholders.</param> + /// <param name="hasOfficialRating">Optional filter by items that have official ratings.</param> + /// <param name="collapseBoxSetItems">Whether or not to hide items behind their boxsets.</param> + /// <param name="minWidth">Optional. Filter by the minimum width of the item.</param> + /// <param name="minHeight">Optional. Filter by the minimum height of the item.</param> + /// <param name="maxWidth">Optional. Filter by the maximum width of the item.</param> + /// <param name="maxHeight">Optional. Filter by the maximum height of the item.</param> + /// <param name="is3D">Optional filter by items that are 3D, or not.</param> + /// <param name="seriesStatus">Optional filter by Series Status. Allows multiple, comma delimited.</param> + /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param> + /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param> + /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param> + /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.</param> + /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.</param> + /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param> + /// <param name="enableImages">Optional, include image information in output.</param> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns> + [HttpGet("Items")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetItems( + [FromQuery] Guid? userId, + [FromQuery] string? maxOfficialRating, + [FromQuery] bool? hasThemeSong, + [FromQuery] bool? hasThemeVideo, + [FromQuery] bool? hasSubtitles, + [FromQuery] bool? hasSpecialFeature, + [FromQuery] bool? hasTrailer, + [FromQuery] Guid? adjacentTo, + [FromQuery] int? indexNumber, + [FromQuery] int? parentIndexNumber, + [FromQuery] bool? hasParentalRating, + [FromQuery] bool? isHd, + [FromQuery] bool? is4K, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] locationTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] excludeLocationTypes, + [FromQuery] bool? isMissing, + [FromQuery] bool? isUnaired, + [FromQuery] double? minCommunityRating, + [FromQuery] double? minCriticRating, + [FromQuery] DateTime? minPremiereDate, + [FromQuery] DateTime? minDateLastSaved, + [FromQuery] DateTime? minDateLastSavedForUser, + [FromQuery] DateTime? maxPremiereDate, + [FromQuery] bool? hasOverview, + [FromQuery] bool? hasImdbId, + [FromQuery] bool? hasTmdbId, + [FromQuery] bool? hasTvdbId, + [FromQuery] bool? isMovie, + [FromQuery] bool? isSeries, + [FromQuery] bool? isNews, + [FromQuery] bool? isKids, + [FromQuery] bool? isSports, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeItemIds, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] bool? recursive, + [FromQuery] string? searchTerm, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, + [FromQuery] Guid? parentId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters, + [FromQuery] bool? isFavorite, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] imageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, + [FromQuery] bool? isPlayed, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery] string? person, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] artists, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] artistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumArtistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] contributingArtistIds, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] albums, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] VideoType[] videoTypes, + [FromQuery] string? minOfficialRating, + [FromQuery] bool? isLocked, + [FromQuery] bool? isPlaceHolder, + [FromQuery] bool? hasOfficialRating, + [FromQuery] bool? collapseBoxSetItems, + [FromQuery] int? minWidth, + [FromQuery] int? minHeight, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] bool? is3D, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SeriesStatus[] seriesStatus, + [FromQuery] string? nameStartsWithOrGreater, + [FromQuery] string? nameStartsWith, + [FromQuery] string? nameLessThan, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds, + [FromQuery] bool enableTotalRecordCount = true, + [FromQuery] bool? enableImages = true) + { + var isApiKey = User.GetIsApiKey(); + // if api key is used (auth.IsApiKey == true), then `user` will be null throughout this method + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value) ?? throw new ResourceNotFoundException(); + + // beyond this point, we're either using an api key or we have a valid user + if (!isApiKey && user is null) + { + return BadRequest("userId is required"); + } + + if (user is not null + && user.GetPreference(PreferenceKind.AllowedTags).Length != 0 + && !fields.Contains(ItemFields.Tags)) + { + fields = [..fields, ItemFields.Tags]; + } + + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + + if (includeItemTypes.Length == 1 + && includeItemTypes[0] == BaseItemKind.BoxSet) + { + parentId = null; + } + + var item = _libraryManager.GetParentItem(parentId, userId); + QueryResult<BaseItem> result; + + if (item is not Folder folder) + { + folder = _libraryManager.GetUserRootFolder(); + } + + CollectionType? collectionType = null; + if (folder is IHasCollectionType hasCollectionType) + { + collectionType = hasCollectionType.CollectionType; + } + + if (collectionType == CollectionType.playlists) + { + recursive = true; + includeItemTypes = new[] { BaseItemKind.Playlist }; + } + + if (item is not UserRootFolder + // api keys can always access all folders + && !isApiKey + // check the item is visible for the user + && !item.IsVisible(user)) + { + _logger.LogWarning("{UserName} is not permitted to access Library {ItemName}", user!.Username, item.Name); + return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}."); + } + + if ((recursive.HasValue && recursive.Value) || ids.Length != 0 || item is not UserRootFolder) + { + var query = new InternalItemsQuery(user) + { + IsPlayed = isPlayed, + MediaTypes = mediaTypes, + IncludeItemTypes = includeItemTypes, + ExcludeItemTypes = excludeItemTypes, + Recursive = recursive ?? false, + OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder), + IsFavorite = isFavorite, + Limit = limit, + StartIndex = startIndex, + IsMissing = isMissing, + IsUnaired = isUnaired, + CollapseBoxSetItems = collapseBoxSetItems, + NameLessThan = nameLessThan, + NameStartsWith = nameStartsWith, + NameStartsWithOrGreater = nameStartsWithOrGreater, + HasImdbId = hasImdbId, + IsPlaceHolder = isPlaceHolder, + IsLocked = isLocked, + MinWidth = minWidth, + MinHeight = minHeight, + MaxWidth = maxWidth, + MaxHeight = maxHeight, + Is3D = is3D, + HasTvdbId = hasTvdbId, + HasTmdbId = hasTmdbId, + IsMovie = isMovie, + IsSeries = isSeries, + IsNews = isNews, + IsKids = isKids, + IsSports = isSports, + HasOverview = hasOverview, + HasOfficialRating = hasOfficialRating, + HasParentalRating = hasParentalRating, + HasSpecialFeature = hasSpecialFeature, + HasSubtitles = hasSubtitles, + HasThemeSong = hasThemeSong, + HasThemeVideo = hasThemeVideo, + HasTrailer = hasTrailer, + IsHD = isHd, + Is4K = is4K, + Tags = tags, + OfficialRatings = officialRatings, + Genres = genres, + ArtistIds = artistIds, + AlbumArtistIds = albumArtistIds, + ContributingArtistIds = contributingArtistIds, + GenreIds = genreIds, + StudioIds = studioIds, + Person = person, + PersonIds = personIds, + PersonTypes = personTypes, + Years = years, + ImageTypes = imageTypes, + VideoTypes = videoTypes, + AdjacentTo = adjacentTo, + ItemIds = ids, + MinCommunityRating = minCommunityRating, + MinCriticRating = minCriticRating, + ParentId = parentId ?? Guid.Empty, + IndexNumber = indexNumber, + ParentIndexNumber = parentIndexNumber, + EnableTotalRecordCount = enableTotalRecordCount, + ExcludeItemIds = excludeItemIds, + DtoOptions = dtoOptions, + SearchTerm = searchTerm, + MinDateLastSaved = minDateLastSaved?.ToUniversalTime(), + MinDateLastSavedForUser = minDateLastSavedForUser?.ToUniversalTime(), + MinPremiereDate = minPremiereDate?.ToUniversalTime(), + MaxPremiereDate = maxPremiereDate?.ToUniversalTime(), + }; + + if (ids.Length != 0 || !string.IsNullOrWhiteSpace(searchTerm)) + { + query.CollapseBoxSetItems = false; + } + + foreach (var filter in filters) + { + switch (filter) + { + case ItemFilter.Dislikes: + query.IsLiked = false; + break; + case ItemFilter.IsFavorite: + query.IsFavorite = true; + break; + case ItemFilter.IsFavoriteOrLikes: + query.IsFavoriteOrLiked = true; + break; + case ItemFilter.IsFolder: + query.IsFolder = true; + break; + case ItemFilter.IsNotFolder: + query.IsFolder = false; + break; + case ItemFilter.IsPlayed: + query.IsPlayed = true; + break; + case ItemFilter.IsResumable: + query.IsResumable = true; + break; + case ItemFilter.IsUnplayed: + query.IsPlayed = false; + break; + case ItemFilter.Likes: + query.IsLiked = true; + break; + } + } + + // Filter by Series Status + if (seriesStatus.Length != 0) + { + query.SeriesStatuses = seriesStatus; + } + + // Exclude Blocked Unrated Items + var blockedUnratedItems = user?.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems); + if (blockedUnratedItems is not null) + { + query.BlockUnratedItems = blockedUnratedItems; + } + + // ExcludeLocationTypes + if (excludeLocationTypes.Any(t => t == LocationType.Virtual)) + { + query.IsVirtualItem = false; + } + + if (locationTypes.Length > 0 && locationTypes.Length < 4) + { + query.IsVirtualItem = locationTypes.Contains(LocationType.Virtual); + } + + // Min official rating + if (!string.IsNullOrWhiteSpace(minOfficialRating)) + { + query.MinParentalRating = _localization.GetRatingScore(minOfficialRating); + } + + // Max official rating + if (!string.IsNullOrWhiteSpace(maxOfficialRating)) + { + query.MaxParentalRating = _localization.GetRatingScore(maxOfficialRating); + } + + // Artists + if (artists.Length != 0) + { + query.ArtistIds = artists.Select(i => + { + try + { + return _libraryManager.GetArtist(i, new DtoOptions(false)); + } + catch + { + return null; + } + }).Where(i => i is not null).Select(i => i!.Id).ToArray(); + } + + // ExcludeArtistIds + if (excludeArtistIds.Length != 0) + { + query.ExcludeArtistIds = excludeArtistIds; + } + + if (albumIds.Length != 0) + { + query.AlbumIds = albumIds; + } + + // Albums + if (albums.Length != 0) + { + query.AlbumIds = albums.SelectMany(i => + { + return _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = new[] { BaseItemKind.MusicAlbum }, Name = i, Limit = 1 }); + }).ToArray(); + } + + // Studios + if (studios.Length != 0) + { + query.StudioIds = studios.Select(i => + { + try + { + return _libraryManager.GetStudio(i); + } + catch + { + return null; + } + }).Where(i => i is not null).Select(i => i!.Id).ToArray(); + } + + // Apply default sorting if none requested + if (query.OrderBy.Count == 0) + { + // Albums by artist + if (query.ArtistIds.Length > 0 && query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.MusicAlbum) + { + query.OrderBy = new[] { (ItemSortBy.ProductionYear, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Ascending) }; + } + } + + query.Parent = null; + result = folder.GetItems(query); + } + else + { + var itemsArray = folder.GetChildren(user, true); + result = new QueryResult<BaseItem>(itemsArray); + } + + return new QueryResult<BaseItemDto>( + startIndex, + result.TotalRecordCount, + _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user)); + } + + /// <summary> + /// Gets items based on a query. + /// </summary> + /// <param name="userId">The user id supplied as query parameter.</param> + /// <param name="maxOfficialRating">Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).</param> + /// <param name="hasThemeSong">Optional filter by items with theme songs.</param> + /// <param name="hasThemeVideo">Optional filter by items with theme videos.</param> + /// <param name="hasSubtitles">Optional filter by items with subtitles.</param> + /// <param name="hasSpecialFeature">Optional filter by items with special features.</param> + /// <param name="hasTrailer">Optional filter by items with trailers.</param> + /// <param name="adjacentTo">Optional. Return items that are siblings of a supplied item.</param> + /// <param name="parentIndexNumber">Optional filter by parent index number.</param> + /// <param name="hasParentalRating">Optional filter by items that have or do not have a parental rating.</param> + /// <param name="isHd">Optional filter by items that are HD or not.</param> + /// <param name="is4K">Optional filter by items that are 4K or not.</param> + /// <param name="locationTypes">Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimited.</param> + /// <param name="excludeLocationTypes">Optional. If specified, results will be filtered based on the LocationType. This allows multiple, comma delimited.</param> + /// <param name="isMissing">Optional filter by items that are missing episodes or not.</param> + /// <param name="isUnaired">Optional filter by items that are unaired episodes or not.</param> + /// <param name="minCommunityRating">Optional filter by minimum community rating.</param> + /// <param name="minCriticRating">Optional filter by minimum critic rating.</param> + /// <param name="minPremiereDate">Optional. The minimum premiere date. Format = ISO.</param> + /// <param name="minDateLastSaved">Optional. The minimum last saved date. Format = ISO.</param> + /// <param name="minDateLastSavedForUser">Optional. The minimum last saved date for the current user. Format = ISO.</param> + /// <param name="maxPremiereDate">Optional. The maximum premiere date. Format = ISO.</param> + /// <param name="hasOverview">Optional filter by items that have an overview or not.</param> + /// <param name="hasImdbId">Optional filter by items that have an IMDb id or not.</param> + /// <param name="hasTmdbId">Optional filter by items that have a TMDb id or not.</param> + /// <param name="hasTvdbId">Optional filter by items that have a TVDb id or not.</param> + /// <param name="isMovie">Optional filter for live tv movies.</param> + /// <param name="isSeries">Optional filter for live tv series.</param> + /// <param name="isNews">Optional filter for live tv news.</param> + /// <param name="isKids">Optional filter for live tv kids.</param> + /// <param name="isSports">Optional filter for live tv sports.</param> + /// <param name="excludeItemIds">Optional. If specified, results will be filtered by excluding item ids. This allows multiple, comma delimited.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="recursive">When searching within folders, this determines whether or not the search will be recursive. true/false.</param> + /// <param name="searchTerm">Optional. Filter based on a search term.</param> + /// <param name="sortOrder">Sort Order - Ascending, Descending.</param> + /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param> + /// <param name="excludeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param> + /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimited.</param> + /// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</param> + /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param> + /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param> + /// <param name="imageTypes">Optional. If specified, results will be filtered based on those containing image types. This allows multiple, comma delimited.</param> + /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param> + /// <param name="isPlayed">Optional filter by items that are played, or not.</param> + /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param> + /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.</param> + /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.</param> + /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.</param> + /// <param name="enableUserData">Optional, include user data.</param> + /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="person">Optional. If specified, results will be filtered to include only those containing the specified person.</param> + /// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the specified person id.</param> + /// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.</param> + /// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.</param> + /// <param name="artists">Optional. If specified, results will be filtered based on artists. This allows multiple, pipe delimited.</param> + /// <param name="excludeArtistIds">Optional. If specified, results will be filtered based on artist id. This allows multiple, pipe delimited.</param> + /// <param name="artistIds">Optional. If specified, results will be filtered to include only those containing the specified artist id.</param> + /// <param name="albumArtistIds">Optional. If specified, results will be filtered to include only those containing the specified album artist id.</param> + /// <param name="contributingArtistIds">Optional. If specified, results will be filtered to include only those containing the specified contributing artist id.</param> + /// <param name="albums">Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimited.</param> + /// <param name="albumIds">Optional. If specified, results will be filtered based on album id. This allows multiple, pipe delimited.</param> + /// <param name="ids">Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited.</param> + /// <param name="videoTypes">Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimited.</param> + /// <param name="minOfficialRating">Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).</param> + /// <param name="isLocked">Optional filter by items that are locked.</param> + /// <param name="isPlaceHolder">Optional filter by items that are placeholders.</param> + /// <param name="hasOfficialRating">Optional filter by items that have official ratings.</param> + /// <param name="collapseBoxSetItems">Whether or not to hide items behind their boxsets.</param> + /// <param name="minWidth">Optional. Filter by the minimum width of the item.</param> + /// <param name="minHeight">Optional. Filter by the minimum height of the item.</param> + /// <param name="maxWidth">Optional. Filter by the maximum width of the item.</param> + /// <param name="maxHeight">Optional. Filter by the maximum height of the item.</param> + /// <param name="is3D">Optional filter by items that are 3D, or not.</param> + /// <param name="seriesStatus">Optional filter by Series Status. Allows multiple, comma delimited.</param> + /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param> + /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param> + /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param> + /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.</param> + /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.</param> + /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param> + /// <param name="enableImages">Optional, include image information in output.</param> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns> + [HttpGet("Users/{userId}/Items")] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetItemsByUserIdLegacy( + [FromRoute] Guid userId, + [FromQuery] string? maxOfficialRating, + [FromQuery] bool? hasThemeSong, + [FromQuery] bool? hasThemeVideo, + [FromQuery] bool? hasSubtitles, + [FromQuery] bool? hasSpecialFeature, + [FromQuery] bool? hasTrailer, + [FromQuery] Guid? adjacentTo, + [FromQuery] int? parentIndexNumber, + [FromQuery] bool? hasParentalRating, + [FromQuery] bool? isHd, + [FromQuery] bool? is4K, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] locationTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] excludeLocationTypes, + [FromQuery] bool? isMissing, + [FromQuery] bool? isUnaired, + [FromQuery] double? minCommunityRating, + [FromQuery] double? minCriticRating, + [FromQuery] DateTime? minPremiereDate, + [FromQuery] DateTime? minDateLastSaved, + [FromQuery] DateTime? minDateLastSavedForUser, + [FromQuery] DateTime? maxPremiereDate, + [FromQuery] bool? hasOverview, + [FromQuery] bool? hasImdbId, + [FromQuery] bool? hasTmdbId, + [FromQuery] bool? hasTvdbId, + [FromQuery] bool? isMovie, + [FromQuery] bool? isSeries, + [FromQuery] bool? isNews, + [FromQuery] bool? isKids, + [FromQuery] bool? isSports, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeItemIds, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] bool? recursive, + [FromQuery] string? searchTerm, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, + [FromQuery] Guid? parentId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters, + [FromQuery] bool? isFavorite, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] imageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, + [FromQuery] bool? isPlayed, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery] string? person, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] artists, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] artistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumArtistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] contributingArtistIds, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] albums, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] VideoType[] videoTypes, + [FromQuery] string? minOfficialRating, + [FromQuery] bool? isLocked, + [FromQuery] bool? isPlaceHolder, + [FromQuery] bool? hasOfficialRating, + [FromQuery] bool? collapseBoxSetItems, + [FromQuery] int? minWidth, + [FromQuery] int? minHeight, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] bool? is3D, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SeriesStatus[] seriesStatus, + [FromQuery] string? nameStartsWithOrGreater, + [FromQuery] string? nameStartsWith, + [FromQuery] string? nameLessThan, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds, + [FromQuery] bool enableTotalRecordCount = true, + [FromQuery] bool? enableImages = true) + => GetItems( + userId, + maxOfficialRating, + hasThemeSong, + hasThemeVideo, + hasSubtitles, + hasSpecialFeature, + hasTrailer, + adjacentTo, + null, + parentIndexNumber, + hasParentalRating, + isHd, + is4K, + locationTypes, + excludeLocationTypes, + isMissing, + isUnaired, + minCommunityRating, + minCriticRating, + minPremiereDate, + minDateLastSaved, + minDateLastSavedForUser, + maxPremiereDate, + hasOverview, + hasImdbId, + hasTmdbId, + hasTvdbId, + isMovie, + isSeries, + isNews, + isKids, + isSports, + excludeItemIds, + startIndex, + limit, + recursive, + searchTerm, + sortOrder, + parentId, + fields, + excludeItemTypes, + includeItemTypes, + filters, + isFavorite, + mediaTypes, + imageTypes, + sortBy, + isPlayed, + genres, + officialRatings, + tags, + years, + enableUserData, + imageTypeLimit, + enableImageTypes, + person, + personIds, + personTypes, + studios, + artists, + excludeArtistIds, + artistIds, + albumArtistIds, + contributingArtistIds, + albums, + albumIds, + ids, + videoTypes, + minOfficialRating, + isLocked, + isPlaceHolder, + hasOfficialRating, + collapseBoxSetItems, + minWidth, + minHeight, + maxWidth, + maxHeight, + is3D, + seriesStatus, + nameStartsWithOrGreater, + nameStartsWith, + nameLessThan, + studioIds, + genreIds, + enableTotalRecordCount, + enableImages); + + /// <summary> + /// Gets items based on a query. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="startIndex">The start index.</param> + /// <param name="limit">The item limit.</param> + /// <param name="searchTerm">The search term.</param> + /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param> + /// <param name="mediaTypes">Optional. Filter by MediaType. Allows multiple, comma delimited.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="excludeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param> + /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimited.</param> + /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="excludeActiveSessions">Optional. Whether to exclude the currently active sessions.</param> + /// <response code="200">Items returned.</response> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items that are resumable.</returns> + [HttpGet("UserItems/Resume")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetResumeItems( + [FromQuery] Guid? userId, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] string? searchTerm, + [FromQuery] Guid? parentId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery] bool enableTotalRecordCount = true, + [FromQuery] bool? enableImages = true, + [FromQuery] bool excludeActiveSessions = false) + { + var requestUserId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(requestUserId); + if (user is null) + { + return NotFound(); + } + + var parentIdGuid = parentId ?? Guid.Empty; + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + + var ancestorIds = Array.Empty<Guid>(); + + var excludeFolderIds = user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes); + if (parentIdGuid.IsEmpty() && excludeFolderIds.Length > 0) + { + ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true) + .Where(i => i is Folder) + .Where(i => !excludeFolderIds.Contains(i.Id)) + .Select(i => i.Id) + .ToArray(); + } + + var excludeItemIds = Array.Empty<Guid>(); + if (excludeActiveSessions) + { + excludeItemIds = _sessionManager.Sessions + .Where(s => s.UserId.Equals(requestUserId) && s.NowPlayingItem is not null) + .Select(s => s.NowPlayingItem.Id) + .ToArray(); + } + + var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user) + { + OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) }, + IsResumable = true, + StartIndex = startIndex, + Limit = limit, + ParentId = parentIdGuid, + Recursive = true, + DtoOptions = dtoOptions, + MediaTypes = mediaTypes, + IsVirtualItem = false, + CollapseBoxSetItems = false, + EnableTotalRecordCount = enableTotalRecordCount, + AncestorIds = ancestorIds, + IncludeItemTypes = includeItemTypes, + ExcludeItemTypes = excludeItemTypes, + SearchTerm = searchTerm, + ExcludeItemIds = excludeItemIds + }); + + var returnItems = _dtoService.GetBaseItemDtos(itemsResult.Items, dtoOptions, user); + + return new QueryResult<BaseItemDto>( + startIndex, + itemsResult.TotalRecordCount, + returnItems); + } + + /// <summary> + /// Gets items based on a query. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="startIndex">The start index.</param> + /// <param name="limit">The item limit.</param> + /// <param name="searchTerm">The search term.</param> + /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param> + /// <param name="mediaTypes">Optional. Filter by MediaType. Allows multiple, comma delimited.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="excludeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param> + /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimited.</param> + /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="excludeActiveSessions">Optional. Whether to exclude the currently active sessions.</param> + /// <response code="200">Items returned.</response> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items that are resumable.</returns> + [HttpGet("Users/{userId}/Items/Resume")] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetResumeItemsLegacy( + [FromRoute, Required] Guid userId, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] string? searchTerm, + [FromQuery] Guid? parentId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery] bool enableTotalRecordCount = true, + [FromQuery] bool? enableImages = true, + [FromQuery] bool excludeActiveSessions = false) + => GetResumeItems( + userId, + startIndex, + limit, + searchTerm, + parentId, + fields, + mediaTypes, + enableUserData, + imageTypeLimit, + enableImageTypes, + excludeItemTypes, + includeItemTypes, + enableTotalRecordCount, + enableImages, + excludeActiveSessions); + + /// <summary> + /// Get Item User Data. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="itemId">The item id.</param> + /// <response code="200">return item user data.</response> + /// <response code="404">Item is not found.</response> + /// <returns>Return <see cref="UserItemDataDto"/>.</returns> + [HttpGet("UserItems/{itemId}/UserData")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<UserItemDataDto?> GetItemUserData( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId) + { + var requestUserId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(requestUserId); + if (user is null) + { + return NotFound(); + } + + if (!RequestHelpers.AssertCanUpdateUser(User, user, true)) + { + return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to view this item user data."); + } + + var item = _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + return _userDataRepository.GetUserDataDto(item, user); + } + + /// <summary> + /// Get Item User Data. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="itemId">The item id.</param> + /// <response code="200">return item user data.</response> + /// <response code="404">Item is not found.</response> + /// <returns>Return <see cref="UserItemDataDto"/>.</returns> + [HttpGet("Users/{userId}/Items/{itemId}/UserData")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult<UserItemDataDto?> GetItemUserDataLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId) + => GetItemUserData(userId, itemId); + + /// <summary> + /// Update Item User Data. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="itemId">The item id.</param> + /// <param name="userDataDto">New user data object.</param> + /// <response code="200">return updated user item data.</response> + /// <response code="404">Item is not found.</response> + /// <returns>Return <see cref="UserItemDataDto"/>.</returns> + [HttpPost("UserItems/{itemId}/UserData")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<UserItemDataDto?> UpdateItemUserData( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId, + [FromBody, Required] UpdateUserItemDataDto userDataDto) + { + var requestUserId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(requestUserId); + if (user is null) + { + return NotFound(); + } + + if (!RequestHelpers.AssertCanUpdateUser(User, user, true)) + { + return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update this item user data."); + } + + var item = _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + _userDataRepository.SaveUserData(user, item, userDataDto, UserDataSaveReason.UpdateUserData); + + return _userDataRepository.GetUserDataDto(item, user); + } + + /// <summary> + /// Update Item User Data. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="itemId">The item id.</param> + /// <param name="userDataDto">New user data object.</param> + /// <response code="200">return updated user item data.</response> + /// <response code="404">Item is not found.</response> + /// <returns>Return <see cref="UserItemDataDto"/>.</returns> + [HttpPost("Users/{userId}/Items/{itemId}/UserData")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult<UserItemDataDto?> UpdateItemUserDataLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId, + [FromBody, Required] UpdateUserItemDataDto userDataDto) + => UpdateItemUserData(userId, itemId, userDataDto); +} diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs new file mode 100644 index 00000000..558e1c6c --- /dev/null +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -0,0 +1,1057 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Attributes; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Api.Models.LibraryDtos; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Common.Api; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Library Controller. +/// </summary> +[Route("")] +public class LibraryController : BaseJellyfinApiController +{ + private readonly IProviderManager _providerManager; + private readonly ILibraryManager _libraryManager; + private readonly IUserManager _userManager; + private readonly IDtoService _dtoService; + private readonly IActivityManager _activityManager; + private readonly ILocalizationManager _localization; + private readonly ILibraryMonitor _libraryMonitor; + private readonly ILogger<LibraryController> _logger; + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="LibraryController"/> class. + /// </summary> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> + /// <param name="activityManager">Instance of the <see cref="IActivityManager"/> interface.</param> + /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> + /// <param name="libraryMonitor">Instance of the <see cref="ILibraryMonitor"/> interface.</param> + /// <param name="logger">Instance of the <see cref="ILogger{LibraryController}"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + public LibraryController( + IProviderManager providerManager, + ILibraryManager libraryManager, + IUserManager userManager, + IDtoService dtoService, + IActivityManager activityManager, + ILocalizationManager localization, + ILibraryMonitor libraryMonitor, + ILogger<LibraryController> logger, + IServerConfigurationManager serverConfigurationManager) + { + _providerManager = providerManager; + _libraryManager = libraryManager; + _userManager = userManager; + _dtoService = dtoService; + _activityManager = activityManager; + _localization = localization; + _libraryMonitor = libraryMonitor; + _logger = logger; + _serverConfigurationManager = serverConfigurationManager; + } + + /// <summary> + /// Get the original file of an item. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <response code="200">File stream returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="FileStreamResult"/> with the original file.</returns> + [HttpGet("Items/{itemId}/File")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesFile("video/*", "audio/*")] + public ActionResult GetFile([FromRoute, Required] Guid itemId) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path), true); + } + + /// <summary> + /// Gets critic review for an item. + /// </summary> + /// <response code="200">Critic reviews returned.</response> + /// <returns>The list of critic reviews.</returns> + [HttpGet("Items/{itemId}/CriticReviews")] + [Authorize] + [Obsolete("This endpoint is obsolete.")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetCriticReviews() + { + return new QueryResult<BaseItemDto>(); + } + + /// <summary> + /// Get theme songs for an item. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <param name="inheritFromParent">Optional. Determines whether or not parent items should be searched for theme media.</param> + /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param> + /// <param name="sortOrder">Optional. Sort Order - Ascending, Descending.</param> + /// <response code="200">Theme songs returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>The item theme songs.</returns> + [HttpGet("Items/{itemId}/ThemeSongs")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<ThemeMediaResult> GetThemeSongs( + [FromRoute, Required] Guid itemId, + [FromQuery] Guid? userId, + [FromQuery] bool inheritFromParent = false, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[]? sortBy = null, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[]? sortOrder = null) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + + var item = itemId.IsEmpty() + ? (userId.IsNullOrEmpty() + ? _libraryManager.RootFolder + : _libraryManager.GetUserRootFolder()) + : _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + sortOrder ??= []; + sortBy ??= []; + var orderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder); + + IReadOnlyList<BaseItem> themeItems; + + while (true) + { + themeItems = item.GetThemeSongs(user, orderBy); + + if (themeItems.Count > 0 || !inheritFromParent) + { + break; + } + + var parent = item.GetParent(); + if (parent is null) + { + break; + } + + item = parent; + } + + var dtoOptions = new DtoOptions(); + var items = themeItems + .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)) + .ToArray(); + + return new ThemeMediaResult + { + Items = items, + TotalRecordCount = items.Length, + OwnerId = item.Id + }; + } + + /// <summary> + /// Get theme videos for an item. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <param name="inheritFromParent">Optional. Determines whether or not parent items should be searched for theme media.</param> + /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param> + /// <param name="sortOrder">Optional. Sort Order - Ascending, Descending.</param> + /// <response code="200">Theme videos returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>The item theme videos.</returns> + [HttpGet("Items/{itemId}/ThemeVideos")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<ThemeMediaResult> GetThemeVideos( + [FromRoute, Required] Guid itemId, + [FromQuery] Guid? userId, + [FromQuery] bool inheritFromParent = false, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[]? sortBy = null, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[]? sortOrder = null) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var item = itemId.IsEmpty() + ? (userId.IsNullOrEmpty() + ? _libraryManager.RootFolder + : _libraryManager.GetUserRootFolder()) + : _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + sortOrder ??= []; + sortBy ??= []; + var orderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder); + + IEnumerable<BaseItem> themeItems; + + while (true) + { + themeItems = item.GetThemeVideos(user, orderBy); + + if (themeItems.Any() || !inheritFromParent) + { + break; + } + + var parent = item.GetParent(); + if (parent is null) + { + break; + } + + item = parent; + } + + var dtoOptions = new DtoOptions(); + var items = themeItems + .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)) + .ToArray(); + + return new ThemeMediaResult + { + Items = items, + TotalRecordCount = items.Length, + OwnerId = item.Id + }; + } + + /// <summary> + /// Get theme songs and videos for an item. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <param name="inheritFromParent">Optional. Determines whether or not parent items should be searched for theme media.</param> + /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param> + /// <param name="sortOrder">Optional. Sort Order - Ascending, Descending.</param> + /// <response code="200">Theme songs and videos returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>The item theme videos.</returns> + [HttpGet("Items/{itemId}/ThemeMedia")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<AllThemeMediaResult> GetThemeMedia( + [FromRoute, Required] Guid itemId, + [FromQuery] Guid? userId, + [FromQuery] bool inheritFromParent = false, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[]? sortBy = null, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[]? sortOrder = null) + { + var themeSongs = GetThemeSongs( + itemId, + userId, + inheritFromParent, + sortBy, + sortOrder); + + var themeVideos = GetThemeVideos( + itemId, + userId, + inheritFromParent, + sortBy, + sortOrder); + + if (themeSongs.Result is StatusCodeResult { StatusCode: StatusCodes.Status404NotFound } + || themeVideos.Result is StatusCodeResult { StatusCode: StatusCodes.Status404NotFound }) + { + return NotFound(); + } + + return new AllThemeMediaResult + { + ThemeSongsResult = themeSongs.Value, + ThemeVideosResult = themeVideos.Value, + SoundtrackSongsResult = new ThemeMediaResult() + }; + } + + /// <summary> + /// Starts a library scan. + /// </summary> + /// <response code="204">Library scan started.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Library/Refresh")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> RefreshLibrary() + { + try + { + await _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error refreshing library"); + } + + return NoContent(); + } + + /// <summary> + /// Deletes an item from the library and filesystem. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <response code="204">Item deleted.</response> + /// <response code="401">Unauthorized access.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpDelete("Items/{itemId}")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult DeleteItem(Guid itemId) + { + var userId = User.GetUserId(); + var isApiKey = User.GetIsApiKey(); + var user = userId.IsEmpty() && isApiKey + ? null + : _userManager.GetUserById(userId); + + if (user is null && !isApiKey) + { + return NotFound(); + } + + var item = _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + if (user is not null && !item.CanDelete(user)) + { + return Unauthorized("Unauthorized access"); + } + + _libraryManager.DeleteItem( + item, + new DeleteOptions { DeleteFileLocation = true }, + true); + + return NoContent(); + } + + /// <summary> + /// Deletes items from the library and filesystem. + /// </summary> + /// <param name="ids">The item ids.</param> + /// <response code="204">Items deleted.</response> + /// <response code="401">Unauthorized access.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpDelete("Items")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids) + { + var isApiKey = User.GetIsApiKey(); + var userId = User.GetUserId(); + var user = !isApiKey && !userId.IsEmpty() + ? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException() + : null; + + if (!isApiKey && user is null) + { + return Unauthorized("Unauthorized access"); + } + + foreach (var i in ids) + { + var item = _libraryManager.GetItemById<BaseItem>(i, user); + if (item is null) + { + return NotFound(); + } + + if (user is not null && !item.CanDelete(user)) + { + return Unauthorized("Unauthorized access"); + } + + _libraryManager.DeleteItem( + item, + new DeleteOptions { DeleteFileLocation = true }, + true); + } + + return NoContent(); + } + + /// <summary> + /// Get item counts. + /// </summary> + /// <param name="userId">Optional. Get counts from a specific user's library.</param> + /// <param name="isFavorite">Optional. Get counts of favorite items.</param> + /// <response code="200">Item counts returned.</response> + /// <returns>Item counts.</returns> + [HttpGet("Items/Counts")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<ItemCounts> GetItemCounts( + [FromQuery] Guid? userId, + [FromQuery] bool? isFavorite) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + + var counts = new ItemCounts + { + AlbumCount = GetCount(BaseItemKind.MusicAlbum, user, isFavorite), + EpisodeCount = GetCount(BaseItemKind.Episode, user, isFavorite), + MovieCount = GetCount(BaseItemKind.Movie, user, isFavorite), + SeriesCount = GetCount(BaseItemKind.Series, user, isFavorite), + SongCount = GetCount(BaseItemKind.Audio, user, isFavorite), + MusicVideoCount = GetCount(BaseItemKind.MusicVideo, user, isFavorite), + BoxSetCount = GetCount(BaseItemKind.BoxSet, user, isFavorite), + BookCount = GetCount(BaseItemKind.Book, user, isFavorite) + }; + + return counts; + } + + /// <summary> + /// Gets all parents of an item. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <response code="200">Item parents returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>Item parents.</returns> + [HttpGet("Items/{itemId}/Ancestors")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var item = _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + var baseItemDtos = new List<BaseItemDto>(); + + var dtoOptions = new DtoOptions(); + BaseItem? parent = item.GetParent(); + + while (parent is not null) + { + if (user is not null) + { + parent = TranslateParentItem(parent, user); + if (parent is null) + { + break; + } + } + + baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user)); + + parent = parent.GetParent(); + } + + return baseItemDtos; + } + + /// <summary> + /// Gets a list of physical paths from virtual folders. + /// </summary> + /// <response code="200">Physical paths returned.</response> + /// <returns>List of physical paths.</returns> + [HttpGet("Library/PhysicalPaths")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<IEnumerable<string>> GetPhysicalPaths() + { + return Ok(_libraryManager.RootFolder.Children + .SelectMany(c => c.PhysicalLocations)); + } + + /// <summary> + /// Gets all user media folders. + /// </summary> + /// <param name="isHidden">Optional. Filter by folders that are marked hidden, or not.</param> + /// <response code="200">Media folders returned.</response> + /// <returns>List of user media folders.</returns> + [HttpGet("Library/MediaFolders")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetMediaFolders([FromQuery] bool? isHidden) + { + var items = _libraryManager.GetUserRootFolder().Children + .Concat(_libraryManager.RootFolder.VirtualChildren) + .Where(i => _libraryManager.GetLibraryOptions(i).Enabled) + .OrderBy(i => i.SortName) + .ToList(); + + if (isHidden.HasValue) + { + var val = isHidden.Value; + + items = items.Where(i => i.IsHidden == val).ToList(); + } + + var dtoOptions = new DtoOptions(); + var resultArray = _dtoService.GetBaseItemDtos(items, dtoOptions); + return new QueryResult<BaseItemDto>(resultArray); + } + + /// <summary> + /// Reports that new episodes of a series have been added by an external source. + /// </summary> + /// <param name="tvdbId">The tvdbId.</param> + /// <response code="204">Report success.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Library/Series/Added", Name = "PostAddedSeries")] + [HttpPost("Library/Series/Updated")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult PostUpdatedSeries([FromQuery] string? tvdbId) + { + var series = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new[] { BaseItemKind.Series }, + DtoOptions = new DtoOptions(false) + { + EnableImages = false + } + }).Where(i => string.Equals(tvdbId, i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Tvdb), StringComparison.OrdinalIgnoreCase)).ToArray(); + + foreach (var item in series) + { + _libraryMonitor.ReportFileSystemChanged(item.Path); + } + + return NoContent(); + } + + /// <summary> + /// Reports that new movies have been added by an external source. + /// </summary> + /// <param name="tmdbId">The tmdbId.</param> + /// <param name="imdbId">The imdbId.</param> + /// <response code="204">Report success.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Library/Movies/Added", Name = "PostAddedMovies")] + [HttpPost("Library/Movies/Updated")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult PostUpdatedMovies([FromQuery] string? tmdbId, [FromQuery] string? imdbId) + { + var movies = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new[] { BaseItemKind.Movie }, + DtoOptions = new DtoOptions(false) + { + EnableImages = false + } + }); + + if (!string.IsNullOrWhiteSpace(imdbId)) + { + movies = movies.Where(i => string.Equals(imdbId, i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb), StringComparison.OrdinalIgnoreCase)).ToList(); + } + else if (!string.IsNullOrWhiteSpace(tmdbId)) + { + movies = movies.Where(i => string.Equals(tmdbId, i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Tmdb), StringComparison.OrdinalIgnoreCase)).ToList(); + } + else + { + movies = new List<BaseItem>(); + } + + foreach (var item in movies) + { + _libraryMonitor.ReportFileSystemChanged(item.Path); + } + + return NoContent(); + } + + /// <summary> + /// Reports that new movies have been added by an external source. + /// </summary> + /// <param name="dto">The update paths.</param> + /// <response code="204">Report success.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Library/Media/Updated")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto dto) + { + foreach (var item in dto.Updates) + { + _libraryMonitor.ReportFileSystemChanged(item.Path ?? throw new ArgumentException("Item path can't be null.")); + } + + return NoContent(); + } + + /// <summary> + /// Downloads item media. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <response code="200">Media downloaded.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="FileResult"/> containing the media stream.</returns> + /// <exception cref="ArgumentException">User can't download or item can't be downloaded.</exception> + [HttpGet("Items/{itemId}/Download")] + [Authorize(Policy = Policies.Download)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesFile("video/*", "audio/*")] + public async Task<ActionResult> GetDownload([FromRoute, Required] Guid itemId) + { + var userId = User.GetUserId(); + var user = userId.IsEmpty() + ? null + : _userManager.GetUserById(userId); + var item = _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + if (user is not null) + { + if (!item.CanDownload(user)) + { + throw new ArgumentException("Item does not support downloading"); + } + } + else + { + if (!item.CanDownload()) + { + throw new ArgumentException("Item does not support downloading"); + } + } + + if (user is not null) + { + await LogDownloadAsync(item, user).ConfigureAwait(false); + } + + // Quotes are valid in linux. They'll possibly cause issues here. + var filename = Path.GetFileName(item.Path)?.Replace("\"", string.Empty, StringComparison.Ordinal); + + var filePath = item.Path; + if (item.IsFileProtocol) + { + // PhysicalFile does not work well with symlinks at the moment. + var resolved = FileSystemHelper.ResolveLinkTarget(filePath, returnFinalTarget: true); + if (resolved is not null && resolved.Exists) + { + filePath = resolved.FullName; + } + } + + return PhysicalFile(filePath, MimeTypes.GetMimeType(filePath), filename, true); + } + + /// <summary> + /// Gets similar items. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="excludeArtistIds">Exclude artist ids.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param> + /// <response code="200">Similar items returned.</response> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> containing the similar items.</returns> + [HttpGet("Artists/{itemId}/Similar", Name = "GetSimilarArtists")] + [HttpGet("Items/{itemId}/Similar")] + [HttpGet("Albums/{itemId}/Similar", Name = "GetSimilarAlbums")] + [HttpGet("Shows/{itemId}/Similar", Name = "GetSimilarShows")] + [HttpGet("Movies/{itemId}/Similar", Name = "GetSimilarMovies")] + [HttpGet("Trailers/{itemId}/Similar", Name = "GetSimilarTrailers")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems( + [FromRoute, Required] Guid itemId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds, + [FromQuery] Guid? userId, + [FromQuery] int? limit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var item = itemId.IsEmpty() + ? (user is null + ? _libraryManager.RootFolder + : _libraryManager.GetUserRootFolder()) + : _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + if (item is Episode || (item is IItemByName && item is not MusicArtist)) + { + return new QueryResult<BaseItemDto>(); + } + + var dtoOptions = new DtoOptions { Fields = fields }; + + var program = item as IHasProgramAttributes; + bool? isMovie = item is Movie || (program is not null && program.IsMovie) || item is Trailer; + bool? isSeries = item is Series || (program is not null && program.IsSeries); + + var includeItemTypes = new List<BaseItemKind>(); + if (isMovie.Value) + { + includeItemTypes.Add(BaseItemKind.Movie); + if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions) + { + includeItemTypes.Add(BaseItemKind.Trailer); + includeItemTypes.Add(BaseItemKind.LiveTvProgram); + } + } + else if (isSeries.Value) + { + includeItemTypes.Add(BaseItemKind.Series); + } + else + { + // For non series and movie types these columns are typically null + // isSeries = null; + isMovie = null; + includeItemTypes.Add(item.GetBaseItemKind()); + } + + var query = new InternalItemsQuery(user) + { + Genres = item.Genres, + Tags = item.Tags, + Limit = limit, + IncludeItemTypes = includeItemTypes.ToArray(), + DtoOptions = dtoOptions, + EnableTotalRecordCount = !isMovie ?? true, + EnableGroupByMetadataKey = isMovie ?? false, + ExcludeItemIds = [itemId], + OrderBy = [(ItemSortBy.Random, SortOrder.Ascending)] + }; + + // ExcludeArtistIds + if (excludeArtistIds.Length != 0) + { + query.ExcludeArtistIds = excludeArtistIds; + } + + var itemsResult = _libraryManager.GetItemList(query); + + var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user); + + return new QueryResult<BaseItemDto>( + query.StartIndex, + itemsResult.Count, + returnList); + } + + /// <summary> + /// Gets the library options info. + /// </summary> + /// <param name="libraryContentType">Library content type.</param> + /// <param name="isNewLibrary">Whether this is a new library.</param> + /// <response code="200">Library options info returned.</response> + /// <returns>Library options info.</returns> + [HttpGet("Libraries/AvailableOptions")] + [Authorize(Policy = Policies.FirstTimeSetupOrDefault)] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<LibraryOptionsResultDto> GetLibraryOptionsInfo( + [FromQuery] CollectionType? libraryContentType, + [FromQuery] bool isNewLibrary = false) + { + var result = new LibraryOptionsResultDto(); + + var types = GetRepresentativeItemTypes(libraryContentType); + var typesList = types.ToList(); + + var plugins = _providerManager.GetAllMetadataPlugins() + .Where(i => types.Contains(i.ItemType, StringComparison.OrdinalIgnoreCase)) + .OrderBy(i => typesList.IndexOf(i.ItemType)) + .ToList(); + + result.MetadataSavers = plugins + .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataSaver)) + .Select(i => new LibraryOptionInfoDto + { + Name = i.Name, + DefaultEnabled = IsSaverEnabledByDefault(i.Name, types, isNewLibrary) + }) + .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .ToArray(); + + result.MetadataReaders = plugins + .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.LocalMetadataProvider)) + .Select(i => new LibraryOptionInfoDto + { + Name = i.Name, + DefaultEnabled = true + }) + .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .ToArray(); + + result.SubtitleFetchers = plugins + .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.SubtitleFetcher)) + .Select(i => new LibraryOptionInfoDto + { + Name = i.Name, + DefaultEnabled = true + }) + .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .ToArray(); + + result.LyricFetchers = plugins + .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.LyricFetcher)) + .Select(i => new LibraryOptionInfoDto + { + Name = i.Name, + DefaultEnabled = true + }) + .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .ToArray(); + + result.MediaSegmentProviders = plugins + .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MediaSegmentProvider)) + .Select(i => new LibraryOptionInfoDto + { + Name = i.Name, + DefaultEnabled = true + }) + .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .ToArray(); + + var typeOptions = new List<LibraryTypeOptionsDto>(); + + foreach (var type in types) + { + TypeOptions.DefaultImageOptions.TryGetValue(type, out var defaultImageOptions); + + typeOptions.Add(new LibraryTypeOptionsDto + { + Type = type, + + MetadataFetchers = plugins + .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) + .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataFetcher)) + .Select(i => new LibraryOptionInfoDto + { + Name = i.Name, + DefaultEnabled = IsMetadataFetcherEnabledByDefault(i.Name, type, isNewLibrary) + }) + .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .ToArray(), + + ImageFetchers = plugins + .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) + .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.ImageFetcher)) + .Select(i => new LibraryOptionInfoDto + { + Name = i.Name, + DefaultEnabled = IsImageFetcherEnabledByDefault(i.Name, type, isNewLibrary) + }) + .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .ToArray(), + + SupportedImageTypes = plugins + .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) + .SelectMany(i => i.SupportedImageTypes ?? Array.Empty<ImageType>()) + .Distinct() + .ToArray(), + + DefaultImageOptions = defaultImageOptions ?? Array.Empty<ImageOption>() + }); + } + + result.TypeOptions = typeOptions.ToArray(); + + return result; + } + + private int GetCount(BaseItemKind itemKind, User? user, bool? isFavorite) + { + var query = new InternalItemsQuery(user) + { + IncludeItemTypes = new[] { itemKind }, + Limit = 0, + Recursive = true, + IsVirtualItem = false, + IsFavorite = isFavorite, + DtoOptions = new DtoOptions(false) + { + EnableImages = false + } + }; + + return _libraryManager.GetItemsResult(query).TotalRecordCount; + } + + private BaseItem? TranslateParentItem(BaseItem item, User user) + { + return item.GetParent() is AggregateFolder + ? _libraryManager.GetUserRootFolder().GetChildren(user, true) + .FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path)) + : item; + } + + private async Task LogDownloadAsync(BaseItem item, User user) + { + try + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Username, item.Name), + "UserDownloadingContent", + User.GetUserId()) + { + ShortOverview = string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("AppDeviceValues"), User.GetClient(), User.GetDevice()), + ItemId = item.Id.ToString("N", CultureInfo.InvariantCulture) + }).ConfigureAwait(false); + } + catch + { + // Logged at lower levels + } + } + + private static string[] GetRepresentativeItemTypes(CollectionType? contentType) + { + return contentType switch + { + CollectionType.boxsets => new[] { "BoxSet" }, + CollectionType.playlists => new[] { "Playlist" }, + CollectionType.movies => new[] { "Movie" }, + CollectionType.tvshows => new[] { "Series", "Season", "Episode" }, + CollectionType.books => new[] { "Book" }, + CollectionType.music => new[] { "MusicArtist", "MusicAlbum", "Audio", "MusicVideo" }, + CollectionType.homevideos => new[] { "Video", "Photo" }, + CollectionType.photos => new[] { "Video", "Photo" }, + CollectionType.musicvideos => new[] { "MusicVideo" }, + _ => new[] { "Series", "Season", "Episode", "Movie" } + }; + } + + private bool IsSaverEnabledByDefault(string name, string[] itemTypes, bool isNewLibrary) + { + if (isNewLibrary) + { + return false; + } + + var metadataOptions = _serverConfigurationManager.Configuration.MetadataOptions + .Where(i => itemTypes.Contains(i.ItemType ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + .ToArray(); + + return metadataOptions.Length == 0 || metadataOptions.Any(i => !i.DisabledMetadataSavers.Contains(name, StringComparison.OrdinalIgnoreCase)); + } + + private bool IsMetadataFetcherEnabledByDefault(string name, string type, bool isNewLibrary) + { + if (isNewLibrary) + { + if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase)) + { + return !(string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase) + || string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase) + || string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase)); + } + + return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase) + || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase) + || string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase); + } + + var metadataOptions = _serverConfigurationManager.GetMetadataOptionsForType(type); + return metadataOptions is null || !metadataOptions.DisabledMetadataFetchers.Contains(name, StringComparison.OrdinalIgnoreCase); + } + + private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary) + { + if (isNewLibrary) + { + if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase)) + { + return !string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase) + && !string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase) + && !string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase) + && !string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase); + } + + return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase) + || string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase) + || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase) + || string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase); + } + + var metadataOptions = _serverConfigurationManager.GetMetadataOptionsForType(type); + return metadataOptions is null || !metadataOptions.DisabledImageFetchers.Contains(name, StringComparison.OrdinalIgnoreCase); + } +} diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs new file mode 100644 index 00000000..11781142 --- /dev/null +++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs @@ -0,0 +1,359 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Api.Models.LibraryStructureDto; +using MediaBrowser.Common.Api; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Entities; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The library structure controller. +/// </summary> +[Route("Library/VirtualFolders")] +[Authorize(Policy = Policies.FirstTimeSetupOrElevated)] +public class LibraryStructureController : BaseJellyfinApiController +{ + private readonly IServerApplicationPaths _appPaths; + private readonly ILibraryManager _libraryManager; + private readonly ILibraryMonitor _libraryMonitor; + + /// <summary> + /// Initializes a new instance of the <see cref="LibraryStructureController"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="libraryManager">Instance of <see cref="ILibraryManager"/> interface.</param> + /// <param name="libraryMonitor">Instance of <see cref="ILibraryMonitor"/> interface.</param> + public LibraryStructureController( + IServerConfigurationManager serverConfigurationManager, + ILibraryManager libraryManager, + ILibraryMonitor libraryMonitor) + { + _appPaths = serverConfigurationManager.ApplicationPaths; + _libraryManager = libraryManager; + _libraryMonitor = libraryMonitor; + } + + /// <summary> + /// Gets all virtual folders. + /// </summary> + /// <response code="200">Virtual folders retrieved.</response> + /// <returns>An <see cref="IEnumerable{VirtualFolderInfo}"/> with the virtual folders.</returns> + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<IEnumerable<VirtualFolderInfo>> GetVirtualFolders() + { + return _libraryManager.GetVirtualFolders(true); + } + + /// <summary> + /// Adds a virtual folder. + /// </summary> + /// <param name="name">The name of the virtual folder.</param> + /// <param name="collectionType">The type of the collection.</param> + /// <param name="paths">The paths of the virtual folder.</param> + /// <param name="libraryOptionsDto">The library options.</param> + /// <param name="refreshLibrary">Whether to refresh the library.</param> + /// <response code="204">Folder added.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> AddVirtualFolder( + [FromQuery] string name, + [FromQuery] CollectionTypeOptions? collectionType, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] paths, + [FromBody] AddVirtualFolderDto? libraryOptionsDto, + [FromQuery] bool refreshLibrary = false) + { + var libraryOptions = libraryOptionsDto?.LibraryOptions ?? new LibraryOptions(); + + if (paths is not null && paths.Length > 0) + { + libraryOptions.PathInfos = Array.ConvertAll(paths, i => new MediaPathInfo(i)); + } + + await _libraryManager.AddVirtualFolder(name, collectionType, libraryOptions, refreshLibrary).ConfigureAwait(false); + + return NoContent(); + } + + /// <summary> + /// Removes a virtual folder. + /// </summary> + /// <param name="name">The name of the folder.</param> + /// <param name="refreshLibrary">Whether to refresh the library.</param> + /// <response code="204">Folder removed.</response> + /// <response code="404">Folder not found.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpDelete] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> RemoveVirtualFolder( + [FromQuery] string name, + [FromQuery] bool refreshLibrary = false) + { + // TODO: refactor! this relies on an FileNotFound exception to return NotFound when attempting to remove a library that does not exist. + await _libraryManager.RemoveVirtualFolder(name, refreshLibrary).ConfigureAwait(false); + + return NoContent(); + } + + /// <summary> + /// Renames a virtual folder. + /// </summary> + /// <param name="name">The name of the virtual folder.</param> + /// <param name="newName">The new name.</param> + /// <param name="refreshLibrary">Whether to refresh the library.</param> + /// <response code="204">Folder renamed.</response> + /// <response code="404">Library doesn't exist.</response> + /// <response code="409">Library already exists.</response> + /// <returns>A <see cref="NoContentResult"/> on success, a <see cref="NotFoundResult"/> if the library doesn't exist, a <see cref="ConflictResult"/> if the new name is already taken.</returns> + /// <exception cref="ArgumentNullException">The new name may not be null.</exception> + [HttpPost("Name")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public ActionResult RenameVirtualFolder( + [FromQuery] string? name, + [FromQuery] string? newName, + [FromQuery] bool refreshLibrary = false) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentNullException(nameof(name)); + } + + if (string.IsNullOrWhiteSpace(newName)) + { + throw new ArgumentNullException(nameof(newName)); + } + + var rootFolderPath = _appPaths.DefaultUserViewsPath; + + var currentPath = Path.Combine(rootFolderPath, name); + var newPath = Path.Combine(rootFolderPath, newName); + + if (!Directory.Exists(currentPath)) + { + return NotFound("The media collection does not exist."); + } + + if (!string.Equals(currentPath, newPath, StringComparison.OrdinalIgnoreCase) && Directory.Exists(newPath)) + { + return Conflict($"The media library already exists at {newPath}."); + } + + _libraryMonitor.Stop(); + + try + { + // Changing capitalization. Handle windows case insensitivity + if (string.Equals(currentPath, newPath, StringComparison.OrdinalIgnoreCase)) + { + var tempPath = Path.Combine( + rootFolderPath, + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)); + Directory.Move(currentPath, tempPath); + currentPath = tempPath; + } + + Directory.Move(currentPath, newPath); + } + finally + { + CollectionFolder.OnCollectionFolderChange(); + + Task.Run(async () => + { + // No need to start if scanning the library because it will handle it + if (refreshLibrary) + { + await _libraryManager.ValidateTopLibraryFolders(CancellationToken.None, true).ConfigureAwait(false); + var newLib = _libraryManager.GetUserRootFolder().Children.FirstOrDefault(f => f.Path.Equals(newPath, StringComparison.OrdinalIgnoreCase)); + if (newLib is CollectionFolder folder) + { + foreach (var child in folder.GetPhysicalFolders()) + { + await child.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); + await child.ValidateChildren(new Progress<double>(), CancellationToken.None).ConfigureAwait(false); + } + } + else + { + // We don't know if this one can be validated individually, trigger a new validation + await _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None).ConfigureAwait(false); + } + } + else + { + // Need to add a delay here or directory watchers may still pick up the changes + // Have to block here to allow exceptions to bubble + await Task.Delay(1000).ConfigureAwait(false); + _libraryMonitor.Start(); + } + }); + } + + return NoContent(); + } + + /// <summary> + /// Add a media path to a library. + /// </summary> + /// <param name="mediaPathDto">The media path dto.</param> + /// <param name="refreshLibrary">Whether to refresh the library.</param> + /// <returns>A <see cref="NoContentResult"/>.</returns> + /// <response code="204">Media path added.</response> + /// <exception cref="ArgumentNullException">The name of the library may not be empty.</exception> + [HttpPost("Paths")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult AddMediaPath( + [FromBody, Required] MediaPathDto mediaPathDto, + [FromQuery] bool refreshLibrary = false) + { + _libraryMonitor.Stop(); + + try + { + var mediaPath = mediaPathDto.PathInfo ?? new MediaPathInfo(mediaPathDto.Path ?? throw new ArgumentException("PathInfo and Path can't both be null.")); + + _libraryManager.AddMediaPath(mediaPathDto.Name, mediaPath); + } + finally + { + Task.Run(async () => + { + // No need to start if scanning the library because it will handle it + if (refreshLibrary) + { + await _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None).ConfigureAwait(false); + } + else + { + // Need to add a delay here or directory watchers may still pick up the changes + // Have to block here to allow exceptions to bubble + await Task.Delay(1000).ConfigureAwait(false); + _libraryMonitor.Start(); + } + }); + } + + return NoContent(); + } + + /// <summary> + /// Updates a media path. + /// </summary> + /// <param name="mediaPathRequestDto">The name of the library and path infos.</param> + /// <returns>A <see cref="NoContentResult"/>.</returns> + /// <response code="204">Media path updated.</response> + /// <exception cref="ArgumentNullException">The name of the library may not be empty.</exception> + [HttpPost("Paths/Update")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult UpdateMediaPath([FromBody, Required] UpdateMediaPathRequestDto mediaPathRequestDto) + { + if (string.IsNullOrWhiteSpace(mediaPathRequestDto.Name)) + { + throw new ArgumentNullException(nameof(mediaPathRequestDto), "Name must not be null or empty"); + } + + _libraryManager.UpdateMediaPath(mediaPathRequestDto.Name, mediaPathRequestDto.PathInfo); + return NoContent(); + } + + /// <summary> + /// Remove a media path. + /// </summary> + /// <param name="name">The name of the library.</param> + /// <param name="path">The path to remove.</param> + /// <param name="refreshLibrary">Whether to refresh the library.</param> + /// <returns>A <see cref="NoContentResult"/>.</returns> + /// <response code="204">Media path removed.</response> + /// <exception cref="ArgumentException">The name of the library and path may not be empty.</exception> + [HttpDelete("Paths")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult RemoveMediaPath( + [FromQuery] string name, + [FromQuery] string path, + [FromQuery] bool refreshLibrary = false) + { + ArgumentException.ThrowIfNullOrWhiteSpace(name); + ArgumentException.ThrowIfNullOrWhiteSpace(path); + + _libraryMonitor.Stop(); + + try + { + _libraryManager.RemoveMediaPath(name, path); + } + finally + { + Task.Run(async () => + { + // No need to start if scanning the library because it will handle it + if (refreshLibrary) + { + await _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None).ConfigureAwait(false); + } + else + { + // Need to add a delay here or directory watchers may still pick up the changes + // Have to block here to allow exceptions to bubble + await Task.Delay(1000).ConfigureAwait(false); + _libraryMonitor.Start(); + } + }); + } + + return NoContent(); + } + + /// <summary> + /// Update library options. + /// </summary> + /// <param name="request">The library name and options.</param> + /// <response code="204">Library updated.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("LibraryOptions")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult UpdateLibraryOptions( + [FromBody] UpdateLibraryOptionsDto request) + { + var item = _libraryManager.GetItemById<CollectionFolder>(request.Id); + if (item is null) + { + return NotFound(); + } + + LibraryOptions options = item.GetLibraryOptions(); + foreach (var mediaPath in request.LibraryOptions!.PathInfos) + { + if (options.PathInfos.Any(i => i.Path == mediaPath.Path)) + { + continue; + } + + _libraryManager.CreateShortcut(item.Path, mediaPath); + } + + item.UpdateLibraryOptions(request.LibraryOptions); + return NoContent(); + } +} diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs new file mode 100644 index 00000000..94f62a07 --- /dev/null +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -0,0 +1,1199 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Net.Http; +using System.Net.Mime; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Attributes; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Api.Models.LiveTvDtos; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Common.Api; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.LiveTv; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Live tv controller. +/// </summary> +public class LiveTvController : BaseJellyfinApiController +{ + private readonly ILiveTvManager _liveTvManager; + private readonly IGuideManager _guideManager; + private readonly ITunerHostManager _tunerHostManager; + private readonly IListingsManager _listingsManager; + private readonly IRecordingsManager _recordingsManager; + private readonly IUserManager _userManager; + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILibraryManager _libraryManager; + private readonly IDtoService _dtoService; + private readonly IMediaSourceManager _mediaSourceManager; + private readonly IConfigurationManager _configurationManager; + private readonly ITranscodeManager _transcodeManager; + + /// <summary> + /// Initializes a new instance of the <see cref="LiveTvController"/> class. + /// </summary> + /// <param name="liveTvManager">Instance of the <see cref="ILiveTvManager"/> interface.</param> + /// <param name="guideManager">Instance of the <see cref="IGuideManager"/> interface.</param> + /// <param name="tunerHostManager">Instance of the <see cref="ITunerHostManager"/> interface.</param> + /// <param name="listingsManager">Instance of the <see cref="IListingsManager"/> interface.</param> + /// <param name="recordingsManager">Instance of the <see cref="IRecordingsManager"/> interface.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> + /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> + /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param> + /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param> + public LiveTvController( + ILiveTvManager liveTvManager, + IGuideManager guideManager, + ITunerHostManager tunerHostManager, + IListingsManager listingsManager, + IRecordingsManager recordingsManager, + IUserManager userManager, + IHttpClientFactory httpClientFactory, + ILibraryManager libraryManager, + IDtoService dtoService, + IMediaSourceManager mediaSourceManager, + IConfigurationManager configurationManager, + ITranscodeManager transcodeManager) + { + _liveTvManager = liveTvManager; + _guideManager = guideManager; + _tunerHostManager = tunerHostManager; + _listingsManager = listingsManager; + _recordingsManager = recordingsManager; + _userManager = userManager; + _httpClientFactory = httpClientFactory; + _libraryManager = libraryManager; + _dtoService = dtoService; + _mediaSourceManager = mediaSourceManager; + _configurationManager = configurationManager; + _transcodeManager = transcodeManager; + } + + /// <summary> + /// Gets available live tv services. + /// </summary> + /// <response code="200">Available live tv services returned.</response> + /// <returns> + /// An <see cref="OkResult"/> containing the available live tv services. + /// </returns> + [HttpGet("Info")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Authorize(Policy = Policies.LiveTvAccess)] + public ActionResult<LiveTvInfo> GetLiveTvInfo() + { + return _liveTvManager.GetLiveTvInfo(CancellationToken.None); + } + + /// <summary> + /// Gets available live tv channels. + /// </summary> + /// <param name="type">Optional. Filter by channel type.</param> + /// <param name="userId">Optional. Filter by user and attach user data.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="isMovie">Optional. Filter for movies.</param> + /// <param name="isSeries">Optional. Filter for series.</param> + /// <param name="isNews">Optional. Filter for news.</param> + /// <param name="isKids">Optional. Filter for kids.</param> + /// <param name="isSports">Optional. Filter for sports.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="isFavorite">Optional. Filter by channels that are favorites, or not.</param> + /// <param name="isLiked">Optional. Filter by channels that are liked, or not.</param> + /// <param name="isDisliked">Optional. Filter by channels that are disliked, or not.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">"Optional. The image types to include in the output.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <param name="sortBy">Optional. Key to sort by.</param> + /// <param name="sortOrder">Optional. Sort order.</param> + /// <param name="enableFavoriteSorting">Optional. Incorporate favorite and like status into channel sorting.</param> + /// <param name="addCurrentProgram">Optional. Adds current program info to each channel.</param> + /// <response code="200">Available live tv channels returned.</response> + /// <returns> + /// An <see cref="OkResult"/> containing the resulting available live tv channels. + /// </returns> + [HttpGet("Channels")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Authorize(Policy = Policies.LiveTvAccess)] + public ActionResult<QueryResult<BaseItemDto>> GetLiveTvChannels( + [FromQuery] ChannelType? type, + [FromQuery] Guid? userId, + [FromQuery] int? startIndex, + [FromQuery] bool? isMovie, + [FromQuery] bool? isSeries, + [FromQuery] bool? isNews, + [FromQuery] bool? isKids, + [FromQuery] bool? isSports, + [FromQuery] int? limit, + [FromQuery] bool? isFavorite, + [FromQuery] bool? isLiked, + [FromQuery] bool? isDisliked, + [FromQuery] bool? enableImages, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] bool? enableUserData, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, + [FromQuery] SortOrder? sortOrder, + [FromQuery] bool enableFavoriteSorting = false, + [FromQuery] bool addCurrentProgram = true) + { + userId = RequestHelpers.GetUserId(User, userId); + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + + var channelResult = _liveTvManager.GetInternalChannels( + new LiveTvChannelQuery + { + ChannelType = type, + UserId = userId.Value, + StartIndex = startIndex, + Limit = limit, + IsFavorite = isFavorite, + IsLiked = isLiked, + IsDisliked = isDisliked, + EnableFavoriteSorting = enableFavoriteSorting, + IsMovie = isMovie, + IsSeries = isSeries, + IsNews = isNews, + IsKids = isKids, + IsSports = isSports, + SortBy = sortBy, + SortOrder = sortOrder ?? SortOrder.Ascending, + AddCurrentProgram = addCurrentProgram + }, + dtoOptions, + CancellationToken.None); + + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + + var fieldsList = dtoOptions.Fields.ToList(); + fieldsList.Remove(ItemFields.CanDelete); + fieldsList.Remove(ItemFields.CanDownload); + fieldsList.Remove(ItemFields.DisplayPreferencesId); + fieldsList.Remove(ItemFields.Etag); + dtoOptions.Fields = fieldsList.ToArray(); + dtoOptions.AddCurrentProgram = addCurrentProgram; + + var returnArray = _dtoService.GetBaseItemDtos(channelResult.Items, dtoOptions, user); + return new QueryResult<BaseItemDto>( + startIndex, + channelResult.TotalRecordCount, + returnArray); + } + + /// <summary> + /// Gets a live tv channel. + /// </summary> + /// <param name="channelId">Channel id.</param> + /// <param name="userId">Optional. Attach user data.</param> + /// <response code="200">Live tv channel returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>An <see cref="OkResult"/> containing the live tv channel.</returns> + [HttpGet("Channels/{channelId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Authorize(Policy = Policies.LiveTvAccess)] + public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var item = channelId.IsEmpty() + ? _libraryManager.GetUserRootFolder() + : _libraryManager.GetItemById<BaseItem>(channelId, user); + + if (item is null) + { + return NotFound(); + } + + var dtoOptions = new DtoOptions(); + return _dtoService.GetBaseItemDto(item, dtoOptions, user); + } + + /// <summary> + /// Gets live tv recordings. + /// </summary> + /// <param name="channelId">Optional. Filter by channel id.</param> + /// <param name="userId">Optional. Filter by user and attach user data.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="status">Optional. Filter by recording status.</param> + /// <param name="isInProgress">Optional. Filter by recordings that are in progress, or not.</param> + /// <param name="seriesTimerId">Optional. Filter by recordings belonging to a series timer.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <param name="isMovie">Optional. Filter for movies.</param> + /// <param name="isSeries">Optional. Filter for series.</param> + /// <param name="isKids">Optional. Filter for kids.</param> + /// <param name="isSports">Optional. Filter for sports.</param> + /// <param name="isNews">Optional. Filter for news.</param> + /// <param name="isLibraryItem">Optional. Filter for is library item.</param> + /// <param name="enableTotalRecordCount">Optional. Return total record count.</param> + /// <response code="200">Live tv recordings returned.</response> + /// <returns>An <see cref="OkResult"/> containing the live tv recordings.</returns> + [HttpGet("Recordings")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Authorize(Policy = Policies.LiveTvAccess)] + public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecordings( + [FromQuery] string? channelId, + [FromQuery] Guid? userId, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] RecordingStatus? status, + [FromQuery] bool? isInProgress, + [FromQuery] string? seriesTimerId, + [FromQuery] bool? enableImages, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] bool? enableUserData, + [FromQuery] bool? isMovie, + [FromQuery] bool? isSeries, + [FromQuery] bool? isKids, + [FromQuery] bool? isSports, + [FromQuery] bool? isNews, + [FromQuery] bool? isLibraryItem, + [FromQuery] bool enableTotalRecordCount = true) + { + userId = RequestHelpers.GetUserId(User, userId); + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + + return await _liveTvManager.GetRecordingsAsync( + new RecordingQuery + { + ChannelId = channelId, + UserId = userId.Value, + StartIndex = startIndex, + Limit = limit, + Status = status, + SeriesTimerId = seriesTimerId, + IsInProgress = isInProgress, + EnableTotalRecordCount = enableTotalRecordCount, + IsMovie = isMovie, + IsNews = isNews, + IsSeries = isSeries, + IsKids = isKids, + IsSports = isSports, + IsLibraryItem = isLibraryItem, + Fields = fields, + ImageTypeLimit = imageTypeLimit, + EnableImages = enableImages + }, + dtoOptions).ConfigureAwait(false); + } + + /// <summary> + /// Gets live tv recording series. + /// </summary> + /// <param name="channelId">Optional. Filter by channel id.</param> + /// <param name="userId">Optional. Filter by user and attach user data.</param> + /// <param name="groupId">Optional. Filter by recording group.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="status">Optional. Filter by recording status.</param> + /// <param name="isInProgress">Optional. Filter by recordings that are in progress, or not.</param> + /// <param name="seriesTimerId">Optional. Filter by recordings belonging to a series timer.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <param name="enableTotalRecordCount">Optional. Return total record count.</param> + /// <response code="200">Live tv recordings returned.</response> + /// <returns>An <see cref="OkResult"/> containing the live tv recordings.</returns> + [HttpGet("Recordings/Series")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Authorize(Policy = Policies.LiveTvAccess)] + [Obsolete("This endpoint is obsolete.")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "channelId", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "groupId", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "startIndex", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "limit", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "status", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "isInProgress", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "seriesTimerId", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableImages", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageTypeLimit", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableImageTypes", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "fields", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableUserData", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableTotalRecordCount", Justification = "Imported from ServiceStack")] + public ActionResult<QueryResult<BaseItemDto>> GetRecordingsSeries( + [FromQuery] string? channelId, + [FromQuery] Guid? userId, + [FromQuery] string? groupId, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] RecordingStatus? status, + [FromQuery] bool? isInProgress, + [FromQuery] string? seriesTimerId, + [FromQuery] bool? enableImages, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] bool? enableUserData, + [FromQuery] bool enableTotalRecordCount = true) + { + return new QueryResult<BaseItemDto>(); + } + + /// <summary> + /// Gets live tv recording groups. + /// </summary> + /// <param name="userId">Optional. Filter by user and attach user data.</param> + /// <response code="200">Recording groups returned.</response> + /// <returns>An <see cref="OkResult"/> containing the recording groups.</returns> + [HttpGet("Recordings/Groups")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Authorize(Policy = Policies.LiveTvAccess)] + [Obsolete("This endpoint is obsolete.")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")] + public ActionResult<QueryResult<BaseItemDto>> GetRecordingGroups([FromQuery] Guid? userId) + { + return new QueryResult<BaseItemDto>(); + } + + /// <summary> + /// Gets recording folders. + /// </summary> + /// <param name="userId">Optional. Filter by user and attach user data.</param> + /// <response code="200">Recording folders returned.</response> + /// <returns>An <see cref="OkResult"/> containing the recording folders.</returns> + [HttpGet("Recordings/Folders")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Authorize(Policy = Policies.LiveTvAccess)] + public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecordingFolders([FromQuery] Guid? userId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var folders = await _liveTvManager.GetRecordingFoldersAsync(user).ConfigureAwait(false); + + var returnArray = _dtoService.GetBaseItemDtos(folders, new DtoOptions(), user); + + return new QueryResult<BaseItemDto>(returnArray); + } + + /// <summary> + /// Gets a live tv recording. + /// </summary> + /// <param name="recordingId">Recording id.</param> + /// <param name="userId">Optional. Attach user data.</param> + /// <response code="200">Recording returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>An <see cref="OkResult"/> containing the live tv recording.</returns> + [HttpGet("Recordings/{recordingId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Authorize(Policy = Policies.LiveTvAccess)] + public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var item = recordingId.IsEmpty() + ? _libraryManager.GetUserRootFolder() + : _libraryManager.GetItemById<BaseItem>(recordingId, user); + if (item is null) + { + return NotFound(); + } + + var dtoOptions = new DtoOptions(); + + return _dtoService.GetBaseItemDto(item, dtoOptions, user); + } + + /// <summary> + /// Resets a tv tuner. + /// </summary> + /// <param name="tunerId">Tuner id.</param> + /// <response code="204">Tuner reset.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Tuners/{tunerId}/Reset")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.LiveTvManagement)] + public async Task<ActionResult> ResetTuner([FromRoute, Required] string tunerId) + { + await _liveTvManager.ResetTuner(tunerId, CancellationToken.None).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Gets a timer. + /// </summary> + /// <param name="timerId">Timer id.</param> + /// <response code="200">Timer returned.</response> + /// <returns> + /// A <see cref="Task"/> containing an <see cref="OkResult"/> which contains the timer. + /// </returns> + [HttpGet("Timers/{timerId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Authorize(Policy = Policies.LiveTvAccess)] + public async Task<ActionResult<TimerInfoDto>> GetTimer([FromRoute, Required] string timerId) + { + return await _liveTvManager.GetTimer(timerId, CancellationToken.None).ConfigureAwait(false); + } + + /// <summary> + /// Gets the default values for a new timer. + /// </summary> + /// <param name="programId">Optional. To attach default values based on a program.</param> + /// <response code="200">Default values returned.</response> + /// <returns> + /// A <see cref="Task"/> containing an <see cref="OkResult"/> which contains the default values for a timer. + /// </returns> + [HttpGet("Timers/Defaults")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Authorize(Policy = Policies.LiveTvAccess)] + public async Task<ActionResult<SeriesTimerInfoDto>> GetDefaultTimer([FromQuery] string? programId) + { + return string.IsNullOrEmpty(programId) + ? await _liveTvManager.GetNewTimerDefaults(CancellationToken.None).ConfigureAwait(false) + : await _liveTvManager.GetNewTimerDefaults(programId, CancellationToken.None).ConfigureAwait(false); + } + + /// <summary> + /// Gets the live tv timers. + /// </summary> + /// <param name="channelId">Optional. Filter by channel id.</param> + /// <param name="seriesTimerId">Optional. Filter by timers belonging to a series timer.</param> + /// <param name="isActive">Optional. Filter by timers that are active.</param> + /// <param name="isScheduled">Optional. Filter by timers that are scheduled.</param> + /// <returns> + /// A <see cref="Task"/> containing an <see cref="OkResult"/> which contains the live tv timers. + /// </returns> + [HttpGet("Timers")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Authorize(Policy = Policies.LiveTvAccess)] + public async Task<ActionResult<QueryResult<TimerInfoDto>>> GetTimers( + [FromQuery] string? channelId, + [FromQuery] string? seriesTimerId, + [FromQuery] bool? isActive, + [FromQuery] bool? isScheduled) + { + return await _liveTvManager.GetTimers( + new TimerQuery + { + ChannelId = channelId, + SeriesTimerId = seriesTimerId, + IsActive = isActive, + IsScheduled = isScheduled + }, + CancellationToken.None).ConfigureAwait(false); + } + + /// <summary> + /// Gets available live tv epgs. + /// </summary> + /// <param name="channelIds">The channels to return guide information for.</param> + /// <param name="userId">Optional. Filter by user id.</param> + /// <param name="minStartDate">Optional. The minimum premiere start date.</param> + /// <param name="hasAired">Optional. Filter by programs that have completed airing, or not.</param> + /// <param name="isAiring">Optional. Filter by programs that are currently airing, or not.</param> + /// <param name="maxStartDate">Optional. The maximum premiere start date.</param> + /// <param name="minEndDate">Optional. The minimum premiere end date.</param> + /// <param name="maxEndDate">Optional. The maximum premiere end date.</param> + /// <param name="isMovie">Optional. Filter for movies.</param> + /// <param name="isSeries">Optional. Filter for series.</param> + /// <param name="isNews">Optional. Filter for news.</param> + /// <param name="isKids">Optional. Filter for kids.</param> + /// <param name="isSports">Optional. Filter for sports.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Name, StartDate.</param> + /// <param name="sortOrder">Sort Order - Ascending,Descending.</param> + /// <param name="genres">The genres to return guide information for.</param> + /// <param name="genreIds">The genre ids to return guide information for.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <param name="seriesTimerId">Optional. Filter by series timer id.</param> + /// <param name="librarySeriesId">Optional. Filter by library series id.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="enableTotalRecordCount">Retrieve total record count.</param> + /// <response code="200">Live tv epgs returned.</response> + /// <returns> + /// A <see cref="Task"/> containing a <see cref="OkResult"/> which contains the live tv epgs. + /// </returns> + [HttpGet("Programs")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Authorize(Policy = Policies.LiveTvAccess)] + public async Task<ActionResult<QueryResult<BaseItemDto>>> GetLiveTvPrograms( + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] channelIds, + [FromQuery] Guid? userId, + [FromQuery] DateTime? minStartDate, + [FromQuery] bool? hasAired, + [FromQuery] bool? isAiring, + [FromQuery] DateTime? maxStartDate, + [FromQuery] DateTime? minEndDate, + [FromQuery] DateTime? maxEndDate, + [FromQuery] bool? isMovie, + [FromQuery] bool? isSeries, + [FromQuery] bool? isNews, + [FromQuery] bool? isKids, + [FromQuery] bool? isSports, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds, + [FromQuery] bool? enableImages, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery] bool? enableUserData, + [FromQuery] string? seriesTimerId, + [FromQuery] Guid? librarySeriesId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] bool enableTotalRecordCount = true) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + + var query = new InternalItemsQuery(user) + { + ChannelIds = channelIds, + HasAired = hasAired, + IsAiring = isAiring, + EnableTotalRecordCount = enableTotalRecordCount, + MinStartDate = minStartDate, + MinEndDate = minEndDate, + MaxStartDate = maxStartDate, + MaxEndDate = maxEndDate, + StartIndex = startIndex, + Limit = limit, + OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder), + IsNews = isNews, + IsMovie = isMovie, + IsSeries = isSeries, + IsKids = isKids, + IsSports = isSports, + SeriesTimerId = seriesTimerId, + Genres = genres, + GenreIds = genreIds + }; + + if (!librarySeriesId.IsNullOrEmpty()) + { + query.IsSeries = true; + + var series = _libraryManager.GetItemById<Series>(librarySeriesId.Value); + if (series is not null) + { + query.Name = series.Name; + } + } + + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false); + } + + /// <summary> + /// Gets available live tv epgs. + /// </summary> + /// <param name="body">Request body.</param> + /// <response code="200">Live tv epgs returned.</response> + /// <returns> + /// A <see cref="Task"/> containing a <see cref="OkResult"/> which contains the live tv epgs. + /// </returns> + [HttpPost("Programs")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Authorize(Policy = Policies.LiveTvAccess)] + public async Task<ActionResult<QueryResult<BaseItemDto>>> GetPrograms([FromBody] GetProgramsDto body) + { + var user = body.UserId.IsNullOrEmpty() ? null : _userManager.GetUserById(body.UserId.Value); + + var query = new InternalItemsQuery(user) + { + ChannelIds = body.ChannelIds ?? [], + HasAired = body.HasAired, + IsAiring = body.IsAiring, + EnableTotalRecordCount = body.EnableTotalRecordCount, + MinStartDate = body.MinStartDate, + MinEndDate = body.MinEndDate, + MaxStartDate = body.MaxStartDate, + MaxEndDate = body.MaxEndDate, + StartIndex = body.StartIndex, + Limit = body.Limit, + OrderBy = RequestHelpers.GetOrderBy(body.SortBy ?? [], body.SortOrder ?? []), + IsNews = body.IsNews, + IsMovie = body.IsMovie, + IsSeries = body.IsSeries, + IsKids = body.IsKids, + IsSports = body.IsSports, + SeriesTimerId = body.SeriesTimerId, + Genres = body.Genres ?? [], + GenreIds = body.GenreIds ?? [] + }; + + if (!body.LibrarySeriesId.IsNullOrEmpty()) + { + query.IsSeries = true; + + var series = _libraryManager.GetItemById<Series>(body.LibrarySeriesId.Value); + if (series is not null) + { + query.Name = series.Name; + } + } + + var dtoOptions = new DtoOptions { Fields = body.Fields ?? [] } + .AddAdditionalDtoOptions(body.EnableImages, body.EnableUserData, body.ImageTypeLimit, body.EnableImageTypes ?? []); + return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false); + } + + /// <summary> + /// Gets recommended live tv epgs. + /// </summary> + /// <param name="userId">Optional. filter by user id.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="isAiring">Optional. Filter by programs that are currently airing, or not.</param> + /// <param name="hasAired">Optional. Filter by programs that have completed airing, or not.</param> + /// <param name="isSeries">Optional. Filter for series.</param> + /// <param name="isMovie">Optional. Filter for movies.</param> + /// <param name="isNews">Optional. Filter for news.</param> + /// <param name="isKids">Optional. Filter for kids.</param> + /// <param name="isSports">Optional. Filter for sports.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="genreIds">The genres to return guide information for.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="enableUserData">Optional. include user data.</param> + /// <param name="enableTotalRecordCount">Retrieve total record count.</param> + /// <response code="200">Recommended epgs returned.</response> + /// <returns>A <see cref="OkResult"/> containing the queryresult of recommended epgs.</returns> + [HttpGet("Programs/Recommended")] + [Authorize(Policy = Policies.LiveTvAccess)] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecommendedPrograms( + [FromQuery] Guid? userId, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] bool? isAiring, + [FromQuery] bool? hasAired, + [FromQuery] bool? isSeries, + [FromQuery] bool? isMovie, + [FromQuery] bool? isNews, + [FromQuery] bool? isKids, + [FromQuery] bool? isSports, + [FromQuery] bool? enableImages, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] bool? enableUserData, + [FromQuery] bool enableTotalRecordCount = true) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + + var query = new InternalItemsQuery(user) + { + IsAiring = isAiring, + StartIndex = startIndex, + Limit = limit, + HasAired = hasAired, + IsSeries = isSeries, + IsMovie = isMovie, + IsKids = isKids, + IsNews = isNews, + IsSports = isSports, + EnableTotalRecordCount = enableTotalRecordCount, + GenreIds = genreIds + }; + + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + return await _liveTvManager.GetRecommendedProgramsAsync(query, dtoOptions, CancellationToken.None).ConfigureAwait(false); + } + + /// <summary> + /// Gets a live tv program. + /// </summary> + /// <param name="programId">Program id.</param> + /// <param name="userId">Optional. Attach user data.</param> + /// <response code="200">Program returned.</response> + /// <returns>An <see cref="OkResult"/> containing the livetv program.</returns> + [HttpGet("Programs/{programId}")] + [Authorize(Policy = Policies.LiveTvAccess)] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<BaseItemDto>> GetProgram( + [FromRoute, Required] string programId, + [FromQuery] Guid? userId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + + return await _liveTvManager.GetProgram(programId, CancellationToken.None, user).ConfigureAwait(false); + } + + /// <summary> + /// Deletes a live tv recording. + /// </summary> + /// <param name="recordingId">Recording id.</param> + /// <response code="204">Recording deleted.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns> + [HttpDelete("Recordings/{recordingId}")] + [Authorize(Policy = Policies.LiveTvManagement)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult DeleteRecording([FromRoute, Required] Guid recordingId) + { + var item = _libraryManager.GetItemById<BaseItem>(recordingId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + _libraryManager.DeleteItem(item, new DeleteOptions + { + DeleteFileLocation = false + }); + + return NoContent(); + } + + /// <summary> + /// Cancels a live tv timer. + /// </summary> + /// <param name="timerId">Timer id.</param> + /// <response code="204">Timer deleted.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpDelete("Timers/{timerId}")] + [Authorize(Policy = Policies.LiveTvManagement)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> CancelTimer([FromRoute, Required] string timerId) + { + await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Updates a live tv timer. + /// </summary> + /// <param name="timerId">Timer id.</param> + /// <param name="timerInfo">New timer info.</param> + /// <response code="204">Timer updated.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Timers/{timerId}")] + [Authorize(Policy = Policies.LiveTvManagement)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")] + public async Task<ActionResult> UpdateTimer([FromRoute, Required] string timerId, [FromBody] TimerInfoDto timerInfo) + { + await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Creates a live tv timer. + /// </summary> + /// <param name="timerInfo">New timer info.</param> + /// <response code="204">Timer created.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Timers")] + [Authorize(Policy = Policies.LiveTvManagement)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> CreateTimer([FromBody] TimerInfoDto timerInfo) + { + await _liveTvManager.CreateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Gets a live tv series timer. + /// </summary> + /// <param name="timerId">Timer id.</param> + /// <response code="200">Series timer returned.</response> + /// <response code="404">Series timer not found.</response> + /// <returns>A <see cref="OkResult"/> on success, or a <see cref="NotFoundResult"/> if timer not found.</returns> + [HttpGet("SeriesTimers/{timerId}")] + [Authorize(Policy = Policies.LiveTvAccess)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult<SeriesTimerInfoDto>> GetSeriesTimer([FromRoute, Required] string timerId) + { + var timer = await _liveTvManager.GetSeriesTimer(timerId, CancellationToken.None).ConfigureAwait(false); + if (timer is null) + { + return NotFound(); + } + + return timer; + } + + /// <summary> + /// Gets live tv series timers. + /// </summary> + /// <param name="sortBy">Optional. Sort by SortName or Priority.</param> + /// <param name="sortOrder">Optional. Sort in Ascending or Descending order.</param> + /// <response code="200">Timers returned.</response> + /// <returns>An <see cref="OkResult"/> of live tv series timers.</returns> + [HttpGet("SeriesTimers")] + [Authorize(Policy = Policies.LiveTvAccess)] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<QueryResult<SeriesTimerInfoDto>>> GetSeriesTimers([FromQuery] string? sortBy, [FromQuery] SortOrder? sortOrder) + { + return await _liveTvManager.GetSeriesTimers( + new SeriesTimerQuery + { + SortOrder = sortOrder ?? SortOrder.Ascending, + SortBy = sortBy + }, + CancellationToken.None).ConfigureAwait(false); + } + + /// <summary> + /// Cancels a live tv series timer. + /// </summary> + /// <param name="timerId">Timer id.</param> + /// <response code="204">Timer cancelled.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpDelete("SeriesTimers/{timerId}")] + [Authorize(Policy = Policies.LiveTvManagement)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> CancelSeriesTimer([FromRoute, Required] string timerId) + { + await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Updates a live tv series timer. + /// </summary> + /// <param name="timerId">Timer id.</param> + /// <param name="seriesTimerInfo">New series timer info.</param> + /// <response code="204">Series timer updated.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("SeriesTimers/{timerId}")] + [Authorize(Policy = Policies.LiveTvManagement)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")] + public async Task<ActionResult> UpdateSeriesTimer([FromRoute, Required] string timerId, [FromBody] SeriesTimerInfoDto seriesTimerInfo) + { + await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Creates a live tv series timer. + /// </summary> + /// <param name="seriesTimerInfo">New series timer info.</param> + /// <response code="204">Series timer info created.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("SeriesTimers")] + [Authorize(Policy = Policies.LiveTvManagement)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> CreateSeriesTimer([FromBody] SeriesTimerInfoDto seriesTimerInfo) + { + await _liveTvManager.CreateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Get recording group. + /// </summary> + /// <param name="groupId">Group id.</param> + /// <returns>A <see cref="NotFoundResult"/>.</returns> + [HttpGet("Recordings/Groups/{groupId}")] + [Authorize(Policy = Policies.LiveTvAccess)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Obsolete("This endpoint is obsolete.")] + public ActionResult<BaseItemDto> GetRecordingGroup([FromRoute, Required] Guid groupId) + { + return NotFound(); + } + + /// <summary> + /// Get guide info. + /// </summary> + /// <response code="200">Guide info returned.</response> + /// <returns>An <see cref="OkResult"/> containing the guide info.</returns> + [HttpGet("GuideInfo")] + [Authorize(Policy = Policies.LiveTvAccess)] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<GuideInfo> GetGuideInfo() + => _guideManager.GetGuideInfo(); + + /// <summary> + /// Adds a tuner host. + /// </summary> + /// <param name="tunerHostInfo">New tuner host.</param> + /// <response code="200">Created tuner host returned.</response> + /// <returns>A <see cref="OkResult"/> containing the created tuner host.</returns> + [HttpPost("TunerHosts")] + [Authorize(Policy = Policies.LiveTvManagement)] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<TunerHostInfo>> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo) + => await _tunerHostManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false); + + /// <summary> + /// Deletes a tuner host. + /// </summary> + /// <param name="id">Tuner host id.</param> + /// <response code="204">Tuner host deleted.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpDelete("TunerHosts")] + [Authorize(Policy = Policies.LiveTvManagement)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult DeleteTunerHost([FromQuery] string? id) + { + var config = _configurationManager.GetConfiguration<LiveTvOptions>("livetv"); + config.TunerHosts = config.TunerHosts.Where(i => !string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)).ToArray(); + _configurationManager.SaveConfiguration("livetv", config); + return NoContent(); + } + + /// <summary> + /// Gets default listings provider info. + /// </summary> + /// <response code="200">Default listings provider info returned.</response> + /// <returns>An <see cref="OkResult"/> containing the default listings provider info.</returns> + [HttpGet("ListingProviders/Default")] + [Authorize(Policy = Policies.LiveTvAccess)] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<ListingsProviderInfo> GetDefaultListingProvider() + { + return new ListingsProviderInfo(); + } + + /// <summary> + /// Adds a listings provider. + /// </summary> + /// <param name="pw">Password.</param> + /// <param name="listingsProviderInfo">New listings info.</param> + /// <param name="validateListings">Validate listings.</param> + /// <param name="validateLogin">Validate login.</param> + /// <response code="200">Created listings provider returned.</response> + /// <returns>A <see cref="OkResult"/> containing the created listings provider.</returns> + [HttpPost("ListingProviders")] + [Authorize(Policy = Policies.LiveTvManagement)] + [ProducesResponseType(StatusCodes.Status200OK)] + [SuppressMessage("Microsoft.Performance", "CA5350:RemoveSha1", MessageId = "AddListingProvider", Justification = "Imported from ServiceStack")] + public async Task<ActionResult<ListingsProviderInfo>> AddListingProvider( + [FromQuery] string? pw, + [FromBody] ListingsProviderInfo listingsProviderInfo, + [FromQuery] bool validateListings = false, + [FromQuery] bool validateLogin = false) + { + if (!string.IsNullOrEmpty(pw)) + { + // TODO: remove ToLower when Convert.ToHexString supports lowercase + // Schedules Direct requires the hex to be lowercase + listingsProviderInfo.Password = Convert.ToHexString(SHA1.HashData(Encoding.UTF8.GetBytes(pw))).ToLowerInvariant(); + } + + return await _listingsManager.SaveListingProvider(listingsProviderInfo, validateLogin, validateListings).ConfigureAwait(false); + } + + /// <summary> + /// Delete listing provider. + /// </summary> + /// <param name="id">Listing provider id.</param> + /// <response code="204">Listing provider deleted.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpDelete("ListingProviders")] + [Authorize(Policy = Policies.LiveTvManagement)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult DeleteListingProvider([FromQuery] string? id) + { + _listingsManager.DeleteListingsProvider(id); + return NoContent(); + } + + /// <summary> + /// Gets available lineups. + /// </summary> + /// <param name="id">Provider id.</param> + /// <param name="type">Provider type.</param> + /// <param name="location">Location.</param> + /// <param name="country">Country.</param> + /// <response code="200">Available lineups returned.</response> + /// <returns>A <see cref="OkResult"/> containing the available lineups.</returns> + [HttpGet("ListingProviders/Lineups")] + [Authorize(Policy = Policies.LiveTvAccess)] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<IEnumerable<NameIdPair>>> GetLineups( + [FromQuery] string? id, + [FromQuery] string? type, + [FromQuery] string? location, + [FromQuery] string? country) + => await _listingsManager.GetLineups(type, id, country, location).ConfigureAwait(false); + + /// <summary> + /// Gets available countries. + /// </summary> + /// <response code="200">Available countries returned.</response> + /// <returns>A <see cref="FileResult"/> containing the available countries.</returns> + [HttpGet("ListingProviders/SchedulesDirect/Countries")] + [Authorize(Policy = Policies.LiveTvAccess)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesFile(MediaTypeNames.Application.Json)] + public async Task<ActionResult> GetSchedulesDirectCountries() + { + var client = _httpClientFactory.CreateClient(NamedClient.Default); + // https://json.schedulesdirect.org/20141201/available/countries + // Can't dispose the response as it's required up the call chain. + var response = await client.GetAsync(new Uri("https://json.schedulesdirect.org/20141201/available/countries")) + .ConfigureAwait(false); + + return File(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), MediaTypeNames.Application.Json); + } + + /// <summary> + /// Get channel mapping options. + /// </summary> + /// <param name="providerId">Provider id.</param> + /// <response code="200">Channel mapping options returned.</response> + /// <returns>An <see cref="OkResult"/> containing the channel mapping options.</returns> + [HttpGet("ChannelMappingOptions")] + [Authorize(Policy = Policies.LiveTvAccess)] + [ProducesResponseType(StatusCodes.Status200OK)] + public Task<ChannelMappingOptionsDto> GetChannelMappingOptions([FromQuery] string? providerId) + => _listingsManager.GetChannelMappingOptions(providerId); + + /// <summary> + /// Set channel mappings. + /// </summary> + /// <param name="dto">The set channel mapping dto.</param> + /// <response code="200">Created channel mapping returned.</response> + /// <returns>An <see cref="OkResult"/> containing the created channel mapping.</returns> + [HttpPost("ChannelMappings")] + [Authorize(Policy = Policies.LiveTvManagement)] + [ProducesResponseType(StatusCodes.Status200OK)] + public Task<TunerChannelMapping> SetChannelMapping([FromBody, Required] SetChannelMappingDto dto) + => _listingsManager.SetChannelMapping(dto.ProviderId, dto.TunerChannelId, dto.ProviderChannelId); + + /// <summary> + /// Get tuner host types. + /// </summary> + /// <response code="200">Tuner host types returned.</response> + /// <returns>An <see cref="OkResult"/> containing the tuner host types.</returns> + [HttpGet("TunerHosts/Types")] + [Authorize(Policy = Policies.LiveTvAccess)] + [ProducesResponseType(StatusCodes.Status200OK)] + public IEnumerable<NameIdPair> GetTunerHostTypes() + => _tunerHostManager.GetTunerHostTypes(); + + /// <summary> + /// Discover tuners. + /// </summary> + /// <param name="newDevicesOnly">Only discover new tuners.</param> + /// <response code="200">Tuners returned.</response> + /// <returns>An <see cref="OkResult"/> containing the tuners.</returns> + [HttpGet("Tuners/Discvover", Name = "DiscvoverTuners")] + [HttpGet("Tuners/Discover")] + [Authorize(Policy = Policies.LiveTvManagement)] + [ProducesResponseType(StatusCodes.Status200OK)] + public IAsyncEnumerable<TunerHostInfo> DiscoverTuners([FromQuery] bool newDevicesOnly = false) + => _tunerHostManager.DiscoverTuners(newDevicesOnly); + + /// <summary> + /// Gets a live tv recording stream. + /// </summary> + /// <param name="recordingId">Recording id.</param> + /// <response code="200">Recording stream returned.</response> + /// <response code="404">Recording not found.</response> + /// <returns> + /// An <see cref="OkResult"/> containing the recording stream on success, + /// or a <see cref="NotFoundResult"/> if recording not found. + /// </returns> + [HttpGet("LiveRecordings/{recordingId}/stream")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesVideoFile] + public ActionResult GetLiveRecordingFile([FromRoute, Required] string recordingId) + { + var path = _recordingsManager.GetActiveRecordingPath(recordingId); + if (string.IsNullOrWhiteSpace(path)) + { + return NotFound(); + } + + var stream = new ProgressiveFileStream(path, null, _transcodeManager); + return new FileStreamResult(stream, MimeTypes.GetMimeType(path)); + } + + /// <summary> + /// Gets a live tv channel stream. + /// </summary> + /// <param name="streamId">Stream id.</param> + /// <param name="container">Container type.</param> + /// <response code="200">Stream returned.</response> + /// <response code="404">Stream not found.</response> + /// <returns> + /// An <see cref="OkResult"/> containing the channel stream on success, + /// or a <see cref="NotFoundResult"/> if stream not found. + /// </returns> + [HttpGet("LiveStreamFiles/{streamId}/stream.{container}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesVideoFile] + public ActionResult GetLiveStreamFile( + [FromRoute, Required] string streamId, + [FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container) + { + var liveStreamInfo = _mediaSourceManager.GetLiveStreamInfoByUniqueId(streamId); + if (liveStreamInfo is null) + { + return NotFound(); + } + + var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream()); + return new FileStreamResult(liveStream, MimeTypes.GetMimeType("file." + container)); + } +} diff --git a/Jellyfin.Api/Controllers/LocalizationController.cs b/Jellyfin.Api/Controllers/LocalizationController.cs new file mode 100644 index 00000000..dd8f935d --- /dev/null +++ b/Jellyfin.Api/Controllers/LocalizationController.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Common.Api; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Localization controller. +/// </summary> +[Authorize(Policy = Policies.FirstTimeSetupOrDefault)] +public class LocalizationController : BaseJellyfinApiController +{ + private readonly ILocalizationManager _localization; + + /// <summary> + /// Initializes a new instance of the <see cref="LocalizationController"/> class. + /// </summary> + /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> + public LocalizationController(ILocalizationManager localization) + { + _localization = localization; + } + + /// <summary> + /// Gets known cultures. + /// </summary> + /// <response code="200">Known cultures returned.</response> + /// <returns>An <see cref="OkResult"/> containing the list of cultures.</returns> + [HttpGet("Cultures")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<IEnumerable<CultureDto>> GetCultures() + { + var allCultures = _localization.GetCultures(); + + var distinctCultures = allCultures + .DistinctBy(c => c.DisplayName, StringComparer.OrdinalIgnoreCase) + .OrderBy(c => c.DisplayName) + .AsEnumerable(); + + return Ok(distinctCultures); + } + + /// <summary> + /// Gets known countries. + /// </summary> + /// <response code="200">Known countries returned.</response> + /// <returns>An <see cref="OkResult"/> containing the list of countries.</returns> + [HttpGet("Countries")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<IReadOnlyList<CountryInfo>> GetCountries() + { + return Ok(_localization.GetCountries()); + } + + /// <summary> + /// Gets known parental ratings. + /// </summary> + /// <response code="200">Known parental ratings returned.</response> + /// <returns>An <see cref="OkResult"/> containing the list of parental ratings.</returns> + [HttpGet("ParentalRatings")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<IReadOnlyList<ParentalRating>> GetParentalRatings() + { + return Ok(_localization.GetParentalRatings()); + } + + /// <summary> + /// Gets localization options. + /// </summary> + /// <response code="200">Localization options returned.</response> + /// <returns>An <see cref="OkResult"/> containing the list of localization options.</returns> + [HttpGet("Options")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<IEnumerable<LocalizationOption>> GetLocalizationOptions() + { + return Ok(_localization.GetLocalizationOptions()); + } +} diff --git a/Jellyfin.Api/Controllers/LyricsController.cs b/Jellyfin.Api/Controllers/LyricsController.cs new file mode 100644 index 00000000..8eb4cadf --- /dev/null +++ b/Jellyfin.Api/Controllers/LyricsController.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Net.Mime; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Attributes; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Extensions; +using MediaBrowser.Common.Api; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Lyrics; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Lyrics; +using MediaBrowser.Model.Providers; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Lyrics controller. +/// </summary> +[Route("")] +public class LyricsController : BaseJellyfinApiController +{ + private readonly ILibraryManager _libraryManager; + private readonly ILyricManager _lyricManager; + private readonly IProviderManager _providerManager; + private readonly IFileSystem _fileSystem; + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="LyricsController"/> class. + /// </summary> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="lyricManager">Instance of the <see cref="ILyricManager"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + public LyricsController( + ILibraryManager libraryManager, + ILyricManager lyricManager, + IProviderManager providerManager, + IFileSystem fileSystem, + IUserManager userManager) + { + _libraryManager = libraryManager; + _lyricManager = lyricManager; + _providerManager = providerManager; + _fileSystem = fileSystem; + _userManager = userManager; + } + + /// <summary> + /// Gets an item's lyrics. + /// </summary> + /// <param name="itemId">Item id.</param> + /// <response code="200">Lyrics returned.</response> + /// <response code="404">Something went wrong. No Lyrics will be returned.</response> + /// <returns>An <see cref="OkResult"/> containing the item's lyrics.</returns> + [HttpGet("Audio/{itemId}/Lyrics")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult<LyricDto>> GetLyrics([FromRoute, Required] Guid itemId) + { + var item = _libraryManager.GetItemById<Audio>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + var result = await _lyricManager.GetLyricsAsync(item, CancellationToken.None).ConfigureAwait(false); + if (result is not null) + { + return Ok(result); + } + + return NotFound(); + } + + /// <summary> + /// Upload an external lyric file. + /// </summary> + /// <param name="itemId">The item the lyric belongs to.</param> + /// <param name="fileName">Name of the file being uploaded.</param> + /// <response code="200">Lyrics uploaded.</response> + /// <response code="400">Error processing upload.</response> + /// <response code="404">Item not found.</response> + /// <returns>The uploaded lyric.</returns> + [HttpPost("Audio/{itemId}/Lyrics")] + [Authorize(Policy = Policies.LyricManagement)] + [AcceptsFile(MediaTypeNames.Text.Plain)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult<LyricDto>> UploadLyrics( + [FromRoute, Required] Guid itemId, + [FromQuery, Required] string fileName) + { + var item = _libraryManager.GetItemById<Audio>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + if (Request.ContentLength.GetValueOrDefault(0) == 0) + { + return BadRequest("No lyrics uploaded"); + } + + // Utilize Path.GetExtension as it provides extra path validation. + var format = Path.GetExtension(fileName.AsSpan()).RightPart('.').ToString(); + if (string.IsNullOrEmpty(format)) + { + return BadRequest("Extension is required on filename"); + } + + var stream = new MemoryStream(); + await using (stream.ConfigureAwait(false)) + { + await Request.Body.CopyToAsync(stream).ConfigureAwait(false); + var uploadedLyric = await _lyricManager.SaveLyricAsync( + item, + format, + stream) + .ConfigureAwait(false); + + if (uploadedLyric is null) + { + return BadRequest(); + } + + _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); + return Ok(uploadedLyric); + } + } + + /// <summary> + /// Deletes an external lyric file. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <response code="204">Lyric deleted.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpDelete("Audio/{itemId}/Lyrics")] + [Authorize(Policy = Policies.LyricManagement)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> DeleteLyrics( + [FromRoute, Required] Guid itemId) + { + var item = _libraryManager.GetItemById<Audio>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + await _lyricManager.DeleteLyricsAsync(item).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Search remote lyrics. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <response code="200">Lyrics retrieved.</response> + /// <response code="404">Item not found.</response> + /// <returns>An array of <see cref="RemoteLyricInfo"/>.</returns> + [HttpGet("Audio/{itemId}/RemoteSearch/Lyrics")] + [Authorize(Policy = Policies.LyricManagement)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult<IReadOnlyList<RemoteLyricInfoDto>>> SearchRemoteLyrics([FromRoute, Required] Guid itemId) + { + var item = _libraryManager.GetItemById<Audio>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + var results = await _lyricManager.SearchLyricsAsync(item, false, CancellationToken.None).ConfigureAwait(false); + return Ok(results); + } + + /// <summary> + /// Downloads a remote lyric. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="lyricId">The lyric id.</param> + /// <response code="200">Lyric downloaded.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Audio/{itemId}/RemoteSearch/Lyrics/{lyricId}")] + [Authorize(Policy = Policies.LyricManagement)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult<LyricDto>> DownloadRemoteLyrics( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] string lyricId) + { + var item = _libraryManager.GetItemById<Audio>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + var downloadedLyrics = await _lyricManager.DownloadLyricsAsync(item, lyricId, CancellationToken.None).ConfigureAwait(false); + if (downloadedLyrics is null) + { + return NotFound(); + } + + _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); + return Ok(downloadedLyrics); + } + + /// <summary> + /// Gets the remote lyrics. + /// </summary> + /// <param name="lyricId">The remote provider item id.</param> + /// <response code="200">File returned.</response> + /// <response code="404">Lyric not found.</response> + /// <returns>A <see cref="FileStreamResult"/> with the lyric file.</returns> + [HttpGet("Providers/Lyrics/{lyricId}")] + [Authorize(Policy = Policies.LyricManagement)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult<LyricDto>> GetRemoteLyrics([FromRoute, Required] string lyricId) + { + var result = await _lyricManager.GetRemoteLyricsAsync(lyricId, CancellationToken.None).ConfigureAwait(false); + if (result is null) + { + return NotFound(); + } + + return Ok(result); + } +} diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs new file mode 100644 index 00000000..f22ac0b7 --- /dev/null +++ b/Jellyfin.Api/Controllers/MediaInfoController.cs @@ -0,0 +1,344 @@ +using System; +using System.Buffers; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Net.Mime; +using System.Threading.Tasks; +using Jellyfin.Api.Attributes; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.Models.MediaInfoDtos; +using Jellyfin.Extensions; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.MediaInfo; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The media info controller. +/// </summary> +[Route("")] +[Authorize] +public class MediaInfoController : BaseJellyfinApiController +{ + private readonly IMediaSourceManager _mediaSourceManager; + private readonly IDeviceManager _deviceManager; + private readonly ILibraryManager _libraryManager; + private readonly ILogger<MediaInfoController> _logger; + private readonly MediaInfoHelper _mediaInfoHelper; + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="MediaInfoController"/> class. + /// </summary> + /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> + /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="logger">Instance of the <see cref="ILogger{MediaInfoController}"/> interface.</param> + /// <param name="mediaInfoHelper">Instance of the <see cref="MediaInfoHelper"/>.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface..</param> + public MediaInfoController( + IMediaSourceManager mediaSourceManager, + IDeviceManager deviceManager, + ILibraryManager libraryManager, + ILogger<MediaInfoController> logger, + MediaInfoHelper mediaInfoHelper, + IUserManager userManager) + { + _mediaSourceManager = mediaSourceManager; + _deviceManager = deviceManager; + _libraryManager = libraryManager; + _logger = logger; + _mediaInfoHelper = mediaInfoHelper; + _userManager = userManager; + } + + /// <summary> + /// Gets live playback media info for an item. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="userId">The user id.</param> + /// <response code="200">Playback info returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns> + [HttpGet("Items/{itemId}/PlaybackInfo")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var item = _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + return await _mediaInfoHelper.GetPlaybackInfo(item, user).ConfigureAwait(false); + } + + /// <summary> + /// Gets live playback media info for an item. + /// </summary> + /// <remarks> + /// For backwards compatibility parameters can be sent via Query or Body, with Query having higher precedence. + /// Query parameters are obsolete. + /// </remarks> + /// <param name="itemId">The item id.</param> + /// <param name="userId">The user id.</param> + /// <param name="maxStreamingBitrate">The maximum streaming bitrate.</param> + /// <param name="startTimeTicks">The start time in ticks.</param> + /// <param name="audioStreamIndex">The audio stream index.</param> + /// <param name="subtitleStreamIndex">The subtitle stream index.</param> + /// <param name="maxAudioChannels">The maximum number of audio channels.</param> + /// <param name="mediaSourceId">The media source id.</param> + /// <param name="liveStreamId">The livestream id.</param> + /// <param name="autoOpenLiveStream">Whether to auto open the livestream.</param> + /// <param name="enableDirectPlay">Whether to enable direct play. Default: true.</param> + /// <param name="enableDirectStream">Whether to enable direct stream. Default: true.</param> + /// <param name="enableTranscoding">Whether to enable transcoding. Default: true.</param> + /// <param name="allowVideoStreamCopy">Whether to allow to copy the video stream. Default: true.</param> + /// <param name="allowAudioStreamCopy">Whether to allow to copy the audio stream. Default: true.</param> + /// <param name="playbackInfoDto">The playback info.</param> + /// <response code="200">Playback info returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback info.</returns> + [HttpPost("Items/{itemId}/PlaybackInfo")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo( + [FromRoute, Required] Guid itemId, + [FromQuery, ParameterObsolete] Guid? userId, + [FromQuery, ParameterObsolete] int? maxStreamingBitrate, + [FromQuery, ParameterObsolete] long? startTimeTicks, + [FromQuery, ParameterObsolete] int? audioStreamIndex, + [FromQuery, ParameterObsolete] int? subtitleStreamIndex, + [FromQuery, ParameterObsolete] int? maxAudioChannels, + [FromQuery, ParameterObsolete] string? mediaSourceId, + [FromQuery, ParameterObsolete] string? liveStreamId, + [FromQuery, ParameterObsolete] bool? autoOpenLiveStream, + [FromQuery, ParameterObsolete] bool? enableDirectPlay, + [FromQuery, ParameterObsolete] bool? enableDirectStream, + [FromQuery, ParameterObsolete] bool? enableTranscoding, + [FromQuery, ParameterObsolete] bool? allowVideoStreamCopy, + [FromQuery, ParameterObsolete] bool? allowAudioStreamCopy, + [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] PlaybackInfoDto? playbackInfoDto) + { + var profile = playbackInfoDto?.DeviceProfile; + _logger.LogDebug("GetPostedPlaybackInfo profile: {@Profile}", profile); + + if (profile is null) + { + var caps = _deviceManager.GetCapabilities(User.GetDeviceId()); + if (caps is not null) + { + profile = caps.DeviceProfile; + } + } + + // Copy params from posted body + // TODO clean up when breaking API compatibility. + userId ??= playbackInfoDto?.UserId; + userId = RequestHelpers.GetUserId(User, userId); + maxStreamingBitrate ??= playbackInfoDto?.MaxStreamingBitrate; + startTimeTicks ??= playbackInfoDto?.StartTimeTicks; + audioStreamIndex ??= playbackInfoDto?.AudioStreamIndex; + subtitleStreamIndex ??= playbackInfoDto?.SubtitleStreamIndex; + maxAudioChannels ??= playbackInfoDto?.MaxAudioChannels; + mediaSourceId ??= playbackInfoDto?.MediaSourceId; + liveStreamId ??= playbackInfoDto?.LiveStreamId; + autoOpenLiveStream ??= playbackInfoDto?.AutoOpenLiveStream ?? false; + enableDirectPlay ??= playbackInfoDto?.EnableDirectPlay ?? true; + enableDirectStream ??= playbackInfoDto?.EnableDirectStream ?? true; + enableTranscoding ??= playbackInfoDto?.EnableTranscoding ?? true; + allowVideoStreamCopy ??= playbackInfoDto?.AllowVideoStreamCopy ?? true; + allowAudioStreamCopy ??= playbackInfoDto?.AllowAudioStreamCopy ?? true; + + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var item = _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + var info = await _mediaInfoHelper.GetPlaybackInfo( + item, + user, + mediaSourceId, + liveStreamId) + .ConfigureAwait(false); + + if (info.ErrorCode is not null) + { + return info; + } + + if (profile is not null) + { + // set device specific data + foreach (var mediaSource in info.MediaSources) + { + _mediaInfoHelper.SetDeviceSpecificData( + item, + mediaSource, + profile, + User, + maxStreamingBitrate ?? profile.MaxStreamingBitrate, + startTimeTicks ?? 0, + mediaSourceId ?? string.Empty, + audioStreamIndex, + subtitleStreamIndex, + maxAudioChannels, + info.PlaySessionId!, + userId ?? Guid.Empty, + enableDirectPlay.Value, + enableDirectStream.Value, + enableTranscoding.Value, + allowVideoStreamCopy.Value, + allowAudioStreamCopy.Value, + playbackInfoDto?.AlwaysBurnInSubtitleWhenTranscoding ?? false, + Request.HttpContext.GetNormalizedRemoteIP()); + } + + _mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate); + } + + if (autoOpenLiveStream.Value) + { + var mediaSource = string.IsNullOrWhiteSpace(mediaSourceId) ? info.MediaSources[0] : info.MediaSources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.Ordinal)); + + if (mediaSource is not null && mediaSource.RequiresOpening && string.IsNullOrWhiteSpace(mediaSource.LiveStreamId)) + { + var openStreamResult = await _mediaInfoHelper.OpenMediaSource( + HttpContext, + new LiveStreamRequest + { + AudioStreamIndex = audioStreamIndex, + DeviceProfile = playbackInfoDto?.DeviceProfile, + EnableDirectPlay = enableDirectPlay.Value, + EnableDirectStream = enableDirectStream.Value, + ItemId = itemId, + MaxAudioChannels = maxAudioChannels, + MaxStreamingBitrate = maxStreamingBitrate, + PlaySessionId = info.PlaySessionId, + StartTimeTicks = startTimeTicks, + SubtitleStreamIndex = subtitleStreamIndex, + UserId = userId ?? Guid.Empty, + OpenToken = mediaSource.OpenToken, + AlwaysBurnInSubtitleWhenTranscoding = playbackInfoDto?.AlwaysBurnInSubtitleWhenTranscoding ?? false + }).ConfigureAwait(false); + + info.MediaSources = new[] { openStreamResult.MediaSource }; + } + } + + return info; + } + + /// <summary> + /// Opens a media source. + /// </summary> + /// <param name="openToken">The open token.</param> + /// <param name="userId">The user id.</param> + /// <param name="playSessionId">The play session id.</param> + /// <param name="maxStreamingBitrate">The maximum streaming bitrate.</param> + /// <param name="startTimeTicks">The start time in ticks.</param> + /// <param name="audioStreamIndex">The audio stream index.</param> + /// <param name="subtitleStreamIndex">The subtitle stream index.</param> + /// <param name="maxAudioChannels">The maximum number of audio channels.</param> + /// <param name="itemId">The item id.</param> + /// <param name="openLiveStreamDto">The open live stream dto.</param> + /// <param name="enableDirectPlay">Whether to enable direct play. Default: true.</param> + /// <param name="enableDirectStream">Whether to enable direct stream. Default: true.</param> + /// <param name="alwaysBurnInSubtitleWhenTranscoding">Always burn-in subtitle when transcoding.</param> + /// <response code="200">Media source opened.</response> + /// <returns>A <see cref="Task"/> containing a <see cref="LiveStreamResponse"/>.</returns> + [HttpPost("LiveStreams/Open")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<LiveStreamResponse>> OpenLiveStream( + [FromQuery] string? openToken, + [FromQuery] Guid? userId, + [FromQuery] string? playSessionId, + [FromQuery] int? maxStreamingBitrate, + [FromQuery] long? startTimeTicks, + [FromQuery] int? audioStreamIndex, + [FromQuery] int? subtitleStreamIndex, + [FromQuery] int? maxAudioChannels, + [FromQuery] Guid? itemId, + [FromBody] OpenLiveStreamDto? openLiveStreamDto, + [FromQuery] bool? enableDirectPlay, + [FromQuery] bool? enableDirectStream, + [FromQuery] bool? alwaysBurnInSubtitleWhenTranscoding) + { + userId ??= openLiveStreamDto?.UserId; + userId = RequestHelpers.GetUserId(User, userId); + var request = new LiveStreamRequest + { + OpenToken = openToken ?? openLiveStreamDto?.OpenToken, + UserId = userId.Value, + PlaySessionId = playSessionId ?? openLiveStreamDto?.PlaySessionId, + MaxStreamingBitrate = maxStreamingBitrate ?? openLiveStreamDto?.MaxStreamingBitrate, + StartTimeTicks = startTimeTicks ?? openLiveStreamDto?.StartTimeTicks, + AudioStreamIndex = audioStreamIndex ?? openLiveStreamDto?.AudioStreamIndex, + SubtitleStreamIndex = subtitleStreamIndex ?? openLiveStreamDto?.SubtitleStreamIndex, + MaxAudioChannels = maxAudioChannels ?? openLiveStreamDto?.MaxAudioChannels, + ItemId = itemId ?? openLiveStreamDto?.ItemId ?? Guid.Empty, + DeviceProfile = openLiveStreamDto?.DeviceProfile, + EnableDirectPlay = enableDirectPlay ?? openLiveStreamDto?.EnableDirectPlay ?? true, + EnableDirectStream = enableDirectStream ?? openLiveStreamDto?.EnableDirectStream ?? true, + DirectPlayProtocols = openLiveStreamDto?.DirectPlayProtocols ?? new[] { MediaProtocol.Http }, + AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding ?? openLiveStreamDto?.AlwaysBurnInSubtitleWhenTranscoding ?? false + }; + return await _mediaInfoHelper.OpenMediaSource(HttpContext, request).ConfigureAwait(false); + } + + /// <summary> + /// Closes a media source. + /// </summary> + /// <param name="liveStreamId">The livestream id.</param> + /// <response code="204">Livestream closed.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("LiveStreams/Close")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> CloseLiveStream([FromQuery, Required] string liveStreamId) + { + await _mediaSourceManager.CloseLiveStream(liveStreamId).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Tests the network with a request with the size of the bitrate. + /// </summary> + /// <param name="size">The bitrate. Defaults to 102400.</param> + /// <response code="200">Test buffer returned.</response> + /// <returns>A <see cref="FileResult"/> with specified bitrate.</returns> + [HttpGet("Playback/BitrateTest")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesFile(MediaTypeNames.Application.Octet)] + public ActionResult GetBitrateTestBytes([FromQuery][Range(1, 100_000_000, ErrorMessage = "The requested size must be greater than or equal to {1} and less than or equal to {2}")] int size = 102400) + { + byte[] buffer = ArrayPool<byte>.Shared.Rent(size); + try + { + Random.Shared.NextBytes(buffer); + return File(buffer, MediaTypeNames.Application.Octet); + } + finally + { + ArrayPool<byte>.Shared.Return(buffer); + } + } +} diff --git a/Jellyfin.Api/Controllers/MediaSegmentsController.cs b/Jellyfin.Api/Controllers/MediaSegmentsController.cs new file mode 100644 index 00000000..b8836d7c --- /dev/null +++ b/Jellyfin.Api/Controllers/MediaSegmentsController.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Api.Extensions; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; +using MediaBrowser.Model.MediaSegments; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Media Segments api. +/// </summary> +[Authorize] +public class MediaSegmentsController : BaseJellyfinApiController +{ + private readonly IMediaSegmentManager _mediaSegmentManager; + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// Initializes a new instance of the <see cref="MediaSegmentsController"/> class. + /// </summary> + /// <param name="mediaSegmentManager">MediaSegments Manager.</param> + /// <param name="libraryManager">The Library manager.</param> + public MediaSegmentsController(IMediaSegmentManager mediaSegmentManager, ILibraryManager libraryManager) + { + _mediaSegmentManager = mediaSegmentManager; + _libraryManager = libraryManager; + } + + /// <summary> + /// Gets all media segments based on an itemId. + /// </summary> + /// <param name="itemId">The ItemId.</param> + /// <param name="includeSegmentTypes">Optional filter of requested segment types.</param> + /// <returns>A list of media segment objects related to the requested itemId.</returns> + [HttpGet("{itemId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult<QueryResult<MediaSegmentDto>>> GetItemSegments( + [FromRoute, Required] Guid itemId, + [FromQuery] IEnumerable<MediaSegmentType>? includeSegmentTypes = null) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + var libraryOptions = _libraryManager.GetLibraryOptions(item); + var items = await _mediaSegmentManager.GetSegmentsAsync(item, includeSegmentTypes, libraryOptions).ConfigureAwait(false); + return Ok(new QueryResult<MediaSegmentDto>(items.ToArray())); + } +} diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs new file mode 100644 index 00000000..ace9a063 --- /dev/null +++ b/Jellyfin.Api/Controllers/MoviesController.cs @@ -0,0 +1,326 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Movies controller. +/// </summary> +[Authorize] +public class MoviesController : BaseJellyfinApiController +{ + private readonly IUserManager _userManager; + private readonly ILibraryManager _libraryManager; + private readonly IDtoService _dtoService; + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="MoviesController"/> class. + /// </summary> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + public MoviesController( + IUserManager userManager, + ILibraryManager libraryManager, + IDtoService dtoService, + IServerConfigurationManager serverConfigurationManager) + { + _userManager = userManager; + _libraryManager = libraryManager; + _dtoService = dtoService; + _serverConfigurationManager = serverConfigurationManager; + } + + /// <summary> + /// Gets movie recommendations. + /// </summary> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param> + /// <param name="fields">Optional. The fields to return.</param> + /// <param name="categoryLimit">The max number of categories to return.</param> + /// <param name="itemLimit">The max number of items to return per category.</param> + /// <response code="200">Movie recommendations returned.</response> + /// <returns>The list of movie recommendations.</returns> + [HttpGet("Recommendations")] + public ActionResult<IEnumerable<RecommendationDto>> GetMovieRecommendations( + [FromQuery] Guid? userId, + [FromQuery] Guid? parentId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] int categoryLimit = 5, + [FromQuery] int itemLimit = 8) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var dtoOptions = new DtoOptions { Fields = fields }; + + var categories = new List<RecommendationDto>(); + + var parentIdGuid = parentId ?? Guid.Empty; + + var query = new InternalItemsQuery(user) + { + IncludeItemTypes = new[] + { + BaseItemKind.Movie, + // nameof(Trailer), + // nameof(LiveTvProgram) + }, + // IsMovie = true + OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.Random, SortOrder.Descending) }, + Limit = 7, + ParentId = parentIdGuid, + Recursive = true, + IsPlayed = true, + DtoOptions = dtoOptions + }; + + var recentlyPlayedMovies = _libraryManager.GetItemList(query); + + var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie }; + if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions) + { + itemTypes.Add(BaseItemKind.Trailer); + itemTypes.Add(BaseItemKind.LiveTvProgram); + } + + var likedMovies = _libraryManager.GetItemList(new InternalItemsQuery(user) + { + IncludeItemTypes = itemTypes.ToArray(), + IsMovie = true, + OrderBy = new[] { (ItemSortBy.Random, SortOrder.Descending) }, + Limit = 10, + IsFavoriteOrLiked = true, + ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id).ToArray(), + EnableGroupByMetadataKey = true, + ParentId = parentIdGuid, + Recursive = true, + DtoOptions = dtoOptions + }); + + var mostRecentMovies = recentlyPlayedMovies.Take(Math.Min(recentlyPlayedMovies.Count, 6)).ToList(); + // Get recently played directors + var recentDirectors = GetDirectors(mostRecentMovies) + .ToList(); + + // Get recently played actors + var recentActors = GetActors(mostRecentMovies) + .ToList(); + + var similarToRecentlyPlayed = GetSimilarTo(user, recentlyPlayedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator(); + var similarToLiked = GetSimilarTo(user, likedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToLikedItem).GetEnumerator(); + + var hasDirectorFromRecentlyPlayed = GetWithDirector(user, recentDirectors, itemLimit, dtoOptions, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator(); + var hasActorFromRecentlyPlayed = GetWithActor(user, recentActors, itemLimit, dtoOptions, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator(); + + var categoryTypes = new List<IEnumerator<RecommendationDto>> + { + // Give this extra weight + similarToRecentlyPlayed, + similarToRecentlyPlayed, + + // Give this extra weight + similarToLiked, + similarToLiked, + hasDirectorFromRecentlyPlayed, + hasActorFromRecentlyPlayed + }; + + while (categories.Count < categoryLimit) + { + var allEmpty = true; + + foreach (var category in categoryTypes) + { + if (category.MoveNext()) + { + categories.Add(category.Current); + allEmpty = false; + + if (categories.Count >= categoryLimit) + { + break; + } + } + } + + if (allEmpty) + { + break; + } + } + + return Ok(categories.OrderBy(i => i.RecommendationType).AsEnumerable()); + } + + private IEnumerable<RecommendationDto> GetWithDirector( + User? user, + IEnumerable<string> names, + int itemLimit, + DtoOptions dtoOptions, + RecommendationType type) + { + var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie }; + if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions) + { + itemTypes.Add(BaseItemKind.Trailer); + itemTypes.Add(BaseItemKind.LiveTvProgram); + } + + foreach (var name in names) + { + var items = _libraryManager.GetItemList( + new InternalItemsQuery(user) + { + Person = name, + // Account for duplicates by IMDb id, since the database doesn't support this yet + Limit = itemLimit + 2, + PersonTypes = new[] { PersonType.Director }, + IncludeItemTypes = itemTypes.ToArray(), + IsMovie = true, + EnableGroupByMetadataKey = true, + DtoOptions = dtoOptions + }).DistinctBy(i => i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)) + .Take(itemLimit) + .ToList(); + + if (items.Count > 0) + { + var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user); + + yield return new RecommendationDto + { + BaselineItemName = name, + CategoryId = name.GetMD5(), + RecommendationType = type, + Items = returnItems + }; + } + } + } + + private IEnumerable<RecommendationDto> GetWithActor(User? user, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type) + { + var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie }; + if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions) + { + itemTypes.Add(BaseItemKind.Trailer); + itemTypes.Add(BaseItemKind.LiveTvProgram); + } + + foreach (var name in names) + { + var items = _libraryManager.GetItemList(new InternalItemsQuery(user) + { + Person = name, + // Account for duplicates by IMDb id, since the database doesn't support this yet + Limit = itemLimit + 2, + IncludeItemTypes = itemTypes.ToArray(), + IsMovie = true, + EnableGroupByMetadataKey = true, + DtoOptions = dtoOptions + }).DistinctBy(i => i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)) + .Take(itemLimit) + .ToList(); + + if (items.Count > 0) + { + var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user); + + yield return new RecommendationDto + { + BaselineItemName = name, + CategoryId = name.GetMD5(), + RecommendationType = type, + Items = returnItems + }; + } + } + } + + private IEnumerable<RecommendationDto> GetSimilarTo(User? user, IEnumerable<BaseItem> baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type) + { + var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie }; + if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions) + { + itemTypes.Add(BaseItemKind.Trailer); + itemTypes.Add(BaseItemKind.LiveTvProgram); + } + + foreach (var item in baselineItems) + { + var similar = _libraryManager.GetItemList(new InternalItemsQuery(user) + { + Limit = itemLimit, + IncludeItemTypes = itemTypes.ToArray(), + IsMovie = true, + EnableGroupByMetadataKey = true, + DtoOptions = dtoOptions + }); + + if (similar.Count > 0) + { + var returnItems = _dtoService.GetBaseItemDtos(similar, dtoOptions, user); + + yield return new RecommendationDto + { + BaselineItemName = item.Name, + CategoryId = item.Id, + RecommendationType = type, + Items = returnItems + }; + } + } + } + + private IEnumerable<string> GetActors(IEnumerable<BaseItem> items) + { + var people = _libraryManager.GetPeople(new InternalPeopleQuery(Array.Empty<string>(), new[] { PersonType.Director }) + { + MaxListOrder = 3 + }); + + var itemIds = items.Select(i => i.Id).ToList(); + + return people + .Where(i => itemIds.Contains(i.ItemId)) + .Select(i => i.Name) + .DistinctNames(); + } + + private IEnumerable<string> GetDirectors(IEnumerable<BaseItem> items) + { + var people = _libraryManager.GetPeople(new InternalPeopleQuery( + new[] { PersonType.Director }, + Array.Empty<string>())); + + var itemIds = items.Select(i => i.Id).ToList(); + + return people + .Where(i => itemIds.Contains(i.ItemId)) + .Select(i => i.Name) + .DistinctNames(); + } +} diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs new file mode 100644 index 00000000..a6427df6 --- /dev/null +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -0,0 +1,204 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The music genres controller. +/// </summary> +[Authorize] +public class MusicGenresController : BaseJellyfinApiController +{ + private readonly ILibraryManager _libraryManager; + private readonly IDtoService _dtoService; + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="MusicGenresController"/> class. + /// </summary> + /// <param name="libraryManager">Instance of <see cref="ILibraryManager"/> interface.</param> + /// <param name="userManager">Instance of <see cref="IUserManager"/> interface.</param> + /// <param name="dtoService">Instance of <see cref="IDtoService"/> interface.</param> + public MusicGenresController( + ILibraryManager libraryManager, + IUserManager userManager, + IDtoService dtoService) + { + _libraryManager = libraryManager; + _userManager = userManager; + _dtoService = dtoService; + } + + /// <summary> + /// Gets all music genres from a given item, folder, or the entire library. + /// </summary> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="searchTerm">The search term.</param> + /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param> + /// <param name="includeItemTypes">Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited.</param> + /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param> + /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="userId">User id.</param> + /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param> + /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param> + /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param> + /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited.</param> + /// <param name="sortOrder">Sort Order - Ascending,Descending.</param> + /// <param name="enableImages">Optional, include image information in output.</param> + /// <param name="enableTotalRecordCount">Optional. Include total record count.</param> + /// <response code="200">Music genres returned.</response> + /// <returns>An <see cref="OkResult"/> containing the queryresult of music genres.</returns> + [HttpGet] + [Obsolete("Use GetGenres instead")] + public ActionResult<QueryResult<BaseItemDto>> GetMusicGenres( + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] string? searchTerm, + [FromQuery] Guid? parentId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery] bool? isFavorite, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery] Guid? userId, + [FromQuery] string? nameStartsWithOrGreater, + [FromQuery] string? nameStartsWith, + [FromQuery] string? nameLessThan, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, + [FromQuery] bool? enableImages = true, + [FromQuery] bool enableTotalRecordCount = true) + { + userId = RequestHelpers.GetUserId(User, userId); + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes); + + User? user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + + var parentItem = _libraryManager.GetParentItem(parentId, userId); + + var query = new InternalItemsQuery(user) + { + ExcludeItemTypes = excludeItemTypes, + IncludeItemTypes = includeItemTypes, + StartIndex = startIndex, + Limit = limit, + IsFavorite = isFavorite, + NameLessThan = nameLessThan, + NameStartsWith = nameStartsWith, + NameStartsWithOrGreater = nameStartsWithOrGreater, + DtoOptions = dtoOptions, + SearchTerm = searchTerm, + EnableTotalRecordCount = enableTotalRecordCount, + OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder) + }; + + if (parentId.HasValue) + { + if (parentItem is Folder) + { + query.AncestorIds = new[] { parentId.Value }; + } + else + { + query.ItemIds = new[] { parentId.Value }; + } + } + + var result = _libraryManager.GetMusicGenres(query); + + var shouldIncludeItemTypes = includeItemTypes.Length != 0; + return RequestHelpers.CreateQueryResult(result, dtoOptions, _dtoService, shouldIncludeItemTypes, user); + } + + /// <summary> + /// Gets a music genre, by name. + /// </summary> + /// <param name="genreName">The genre name.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <returns>An <see cref="OkResult"/> containing a <see cref="BaseItemDto"/> with the music genre.</returns> + [HttpGet("{genreName}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<BaseItemDto> GetMusicGenre([FromRoute, Required] string genreName, [FromQuery] Guid? userId) + { + userId = RequestHelpers.GetUserId(User, userId); + var dtoOptions = new DtoOptions(); + + MusicGenre? item; + + if (genreName.Contains(BaseItem.SlugChar, StringComparison.OrdinalIgnoreCase)) + { + item = GetItemFromSlugName<MusicGenre>(_libraryManager, genreName, dtoOptions, BaseItemKind.MusicGenre); + } + else + { + item = _libraryManager.GetMusicGenre(genreName); + } + + if (item is null) + { + return NotFound(); + } + + if (!userId.IsNullOrEmpty()) + { + var user = _userManager.GetUserById(userId.Value); + + return _dtoService.GetBaseItemDto(item, dtoOptions, user); + } + + return _dtoService.GetBaseItemDto(item, dtoOptions); + } + + private T? GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions, BaseItemKind baseItemKind) + where T : BaseItem, new() + { + var result = libraryManager.GetItemList(new InternalItemsQuery + { + Name = name.Replace(BaseItem.SlugChar, '&'), + IncludeItemTypes = new[] { baseItemKind }, + DtoOptions = dtoOptions + }).OfType<T>().FirstOrDefault(); + + result ??= libraryManager.GetItemList(new InternalItemsQuery + { + Name = name.Replace(BaseItem.SlugChar, '/'), + IncludeItemTypes = new[] { baseItemKind }, + DtoOptions = dtoOptions + }).OfType<T>().FirstOrDefault(); + + result ??= libraryManager.GetItemList(new InternalItemsQuery + { + Name = name.Replace(BaseItem.SlugChar, '?'), + IncludeItemTypes = new[] { baseItemKind }, + DtoOptions = dtoOptions + }).OfType<T>().FirstOrDefault(); + + return result; + } +} diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs new file mode 100644 index 00000000..274e94ee --- /dev/null +++ b/Jellyfin.Api/Controllers/PackageController.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using MediaBrowser.Common.Api; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Updates; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Package Controller. +/// </summary> +[Route("")] +[Authorize(Policy = Policies.RequiresElevation)] +public class PackageController : BaseJellyfinApiController +{ + private readonly IInstallationManager _installationManager; + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PackageController"/> class. + /// </summary> + /// <param name="installationManager">Instance of the <see cref="IInstallationManager"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + public PackageController(IInstallationManager installationManager, IServerConfigurationManager serverConfigurationManager) + { + _installationManager = installationManager; + _serverConfigurationManager = serverConfigurationManager; + } + + /// <summary> + /// Gets a package by name or assembly GUID. + /// </summary> + /// <param name="name">The name of the package.</param> + /// <param name="assemblyGuid">The GUID of the associated assembly.</param> + /// <response code="200">Package retrieved.</response> + /// <returns>A <see cref="PackageInfo"/> containing package information.</returns> + [HttpGet("Packages/{name}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<PackageInfo>> GetPackageInfo( + [FromRoute, Required] string name, + [FromQuery] Guid? assemblyGuid) + { + var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false); + var result = _installationManager.FilterPackages( + packages, + name, + assemblyGuid ?? default) + .FirstOrDefault(); + + if (result is null) + { + return NotFound(); + } + + return result; + } + + /// <summary> + /// Gets available packages. + /// </summary> + /// <response code="200">Available packages returned.</response> + /// <returns>An <see cref="PackageInfo"/> containing available packages information.</returns> + [HttpGet("Packages")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<IEnumerable<PackageInfo>> GetPackages() + { + IEnumerable<PackageInfo> packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false); + + return packages; + } + + /// <summary> + /// Installs a package. + /// </summary> + /// <param name="name">Package name.</param> + /// <param name="assemblyGuid">GUID of the associated assembly.</param> + /// <param name="version">Optional version. Defaults to latest version.</param> + /// <param name="repositoryUrl">Optional. Specify the repository to install from.</param> + /// <response code="204">Package found.</response> + /// <response code="404">Package not found.</response> + /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the package could not be found.</returns> + [HttpPost("Packages/Installed/{name}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> InstallPackage( + [FromRoute, Required] string name, + [FromQuery] Guid? assemblyGuid, + [FromQuery] string? version, + [FromQuery] string? repositoryUrl) + { + var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false); + if (!string.IsNullOrEmpty(repositoryUrl)) + { + packages = packages.Where(p => p.Versions.Any(q => q.RepositoryUrl.Equals(repositoryUrl, StringComparison.OrdinalIgnoreCase))) + .ToList(); + } + + var package = _installationManager.GetCompatibleVersions( + packages, + name, + assemblyGuid ?? Guid.Empty, + specificVersion: string.IsNullOrEmpty(version) ? null : Version.Parse(version)) + .FirstOrDefault(); + + if (package is null) + { + return NotFound(); + } + + await _installationManager.InstallPackage(package).ConfigureAwait(false); + + return NoContent(); + } + + /// <summary> + /// Cancels a package installation. + /// </summary> + /// <param name="packageId">Installation Id.</param> + /// <response code="204">Installation cancelled.</response> + /// <returns>A <see cref="NoContentResult"/> on successfully cancelling a package installation.</returns> + [HttpDelete("Packages/Installing/{packageId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult CancelPackageInstallation( + [FromRoute, Required] Guid packageId) + { + _installationManager.CancelInstallation(packageId); + return NoContent(); + } + + /// <summary> + /// Gets all package repositories. + /// </summary> + /// <response code="200">Package repositories returned.</response> + /// <returns>An <see cref="OkResult"/> containing the list of package repositories.</returns> + [HttpGet("Repositories")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<IEnumerable<RepositoryInfo>> GetRepositories() + { + return Ok(_serverConfigurationManager.Configuration.PluginRepositories.AsEnumerable()); + } + + /// <summary> + /// Sets the enabled and existing package repositories. + /// </summary> + /// <param name="repositoryInfos">The list of package repositories.</param> + /// <response code="204">Package repositories saved.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Repositories")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult SetRepositories([FromBody, Required] RepositoryInfo[] repositoryInfos) + { + _serverConfigurationManager.Configuration.PluginRepositories = repositoryInfos; + _serverConfigurationManager.SaveConfiguration(); + return NoContent(); + } +} diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs new file mode 100644 index 00000000..438d054a --- /dev/null +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -0,0 +1,139 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Persons controller. +/// </summary> +[Authorize] +public class PersonsController : BaseJellyfinApiController +{ + private readonly ILibraryManager _libraryManager; + private readonly IDtoService _dtoService; + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PersonsController"/> class. + /// </summary> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + public PersonsController( + ILibraryManager libraryManager, + IDtoService dtoService, + IUserManager userManager) + { + _libraryManager = libraryManager; + _dtoService = dtoService; + _userManager = userManager; + } + + /// <summary> + /// Gets all persons. + /// </summary> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="searchTerm">The search term.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="filters">Optional. Specify additional filters to apply.</param> + /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not. userId is required.</param> + /// <param name="enableUserData">Optional, include user data.</param> + /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="excludePersonTypes">Optional. If specified results will be filtered to exclude those containing the specified PersonType. Allows multiple, comma-delimited.</param> + /// <param name="personTypes">Optional. If specified results will be filtered to include only those containing the specified PersonType. Allows multiple, comma-delimited.</param> + /// <param name="appearsInItemId">Optional. If specified, person results will be filtered on items related to said persons.</param> + /// <param name="userId">User id.</param> + /// <param name="enableImages">Optional, include image information in output.</param> + /// <response code="200">Persons returned.</response> + /// <returns>An <see cref="OkResult"/> containing the queryresult of persons.</returns> + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetPersons( + [FromQuery] int? limit, + [FromQuery] string? searchTerm, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters, + [FromQuery] bool? isFavorite, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] excludePersonTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes, + [FromQuery] Guid? appearsInItemId, + [FromQuery] Guid? userId, + [FromQuery] bool? enableImages = true) + { + userId = RequestHelpers.GetUserId(User, userId); + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + + User? user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + + var isFavoriteInFilters = filters.Any(f => f == ItemFilter.IsFavorite); + var peopleItems = _libraryManager.GetPeopleItems(new InternalPeopleQuery( + personTypes, + excludePersonTypes) + { + NameContains = searchTerm, + User = user, + IsFavorite = !isFavorite.HasValue && isFavoriteInFilters ? true : isFavorite, + AppearsInItemId = appearsInItemId ?? Guid.Empty, + Limit = limit ?? 0 + }); + + return new QueryResult<BaseItemDto>( + peopleItems + .Select(person => _dtoService.GetItemByNameDto(person, dtoOptions, null, user)) + .ToArray()); + } + + /// <summary> + /// Get person by name. + /// </summary> + /// <param name="name">Person name.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <response code="200">Person returned.</response> + /// <response code="404">Person not found.</response> + /// <returns>An <see cref="OkResult"/> containing the person on success, + /// or a <see cref="NotFoundResult"/> if person not found.</returns> + [HttpGet("{name}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<BaseItemDto> GetPerson([FromRoute, Required] string name, [FromQuery] Guid? userId) + { + userId = RequestHelpers.GetUserId(User, userId); + var dtoOptions = new DtoOptions(); + + var item = _libraryManager.GetPerson(name); + if (item is null) + { + return NotFound(); + } + + if (!userId.IsNullOrEmpty()) + { + var user = _userManager.GetUserById(userId.Value); + return _dtoService.GetBaseItemDto(item, dtoOptions, user); + } + + return _dtoService.GetBaseItemDto(item, dtoOptions); + } +} diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs new file mode 100644 index 00000000..96791809 --- /dev/null +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -0,0 +1,568 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Api.Attributes; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Api.Models.PlaylistDtos; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Playlists; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Playlists controller. +/// </summary> +[Authorize] +public class PlaylistsController : BaseJellyfinApiController +{ + private readonly IPlaylistManager _playlistManager; + private readonly IDtoService _dtoService; + private readonly IUserManager _userManager; + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PlaylistsController"/> class. + /// </summary> + /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> + /// <param name="playlistManager">Instance of the <see cref="IPlaylistManager"/> interface.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + public PlaylistsController( + IDtoService dtoService, + IPlaylistManager playlistManager, + IUserManager userManager, + ILibraryManager libraryManager) + { + _dtoService = dtoService; + _playlistManager = playlistManager; + _userManager = userManager; + _libraryManager = libraryManager; + } + + /// <summary> + /// Creates a new playlist. + /// </summary> + /// <remarks> + /// For backwards compatibility parameters can be sent via Query or Body, with Query having higher precedence. + /// Query parameters are obsolete. + /// </remarks> + /// <param name="name">The playlist name.</param> + /// <param name="ids">The item ids.</param> + /// <param name="userId">The user id.</param> + /// <param name="mediaType">The media type.</param> + /// <param name="createPlaylistRequest">The create playlist payload.</param> + /// <response code="200">Playlist created.</response> + /// <returns> + /// A <see cref="Task" /> that represents the asynchronous operation to create a playlist. + /// The task result contains an <see cref="OkResult"/> indicating success. + /// </returns> + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<PlaylistCreationResult>> CreatePlaylist( + [FromQuery, ParameterObsolete] string? name, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder)), ParameterObsolete] IReadOnlyList<Guid> ids, + [FromQuery, ParameterObsolete] Guid? userId, + [FromQuery, ParameterObsolete] MediaType? mediaType, + [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] CreatePlaylistDto? createPlaylistRequest) + { + if (ids.Count == 0) + { + ids = createPlaylistRequest?.Ids ?? Array.Empty<Guid>(); + } + + userId ??= createPlaylistRequest?.UserId ?? default; + userId = RequestHelpers.GetUserId(User, userId); + var result = await _playlistManager.CreatePlaylist(new PlaylistCreationRequest + { + Name = name ?? createPlaylistRequest?.Name, + ItemIdList = ids, + UserId = userId.Value, + MediaType = mediaType ?? createPlaylistRequest?.MediaType, + Users = createPlaylistRequest?.Users.ToArray() ?? [], + Public = createPlaylistRequest?.IsPublic + }).ConfigureAwait(false); + + return result; + } + + /// <summary> + /// Updates a playlist. + /// </summary> + /// <param name="playlistId">The playlist id.</param> + /// <param name="updatePlaylistRequest">The <see cref="UpdatePlaylistDto"/> id.</param> + /// <response code="204">Playlist updated.</response> + /// <response code="403">Access forbidden.</response> + /// <response code="404">Playlist not found.</response> + /// <returns> + /// A <see cref="Task" /> that represents the asynchronous operation to update a playlist. + /// The task result contains an <see cref="OkResult"/> indicating success. + /// </returns> + [HttpPost("{playlistId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> UpdatePlaylist( + [FromRoute, Required] Guid playlistId, + [FromBody, Required] UpdatePlaylistDto updatePlaylistRequest) + { + var callingUserId = User.GetUserId(); + + var playlist = _playlistManager.GetPlaylistForUser(playlistId, callingUserId); + if (playlist is null) + { + return NotFound("Playlist not found"); + } + + var isPermitted = playlist.OwnerUserId.Equals(callingUserId) + || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId)); + + if (!isPermitted) + { + return Forbid(); + } + + await _playlistManager.UpdatePlaylist(new PlaylistUpdateRequest + { + UserId = callingUserId, + Id = playlistId, + Name = updatePlaylistRequest.Name, + Ids = updatePlaylistRequest.Ids, + Users = updatePlaylistRequest.Users, + Public = updatePlaylistRequest.IsPublic + }).ConfigureAwait(false); + + return NoContent(); + } + + /// <summary> + /// Get a playlist. + /// </summary> + /// <param name="playlistId">The playlist id.</param> + /// <response code="200">The playlist.</response> + /// <response code="404">Playlist not found.</response> + /// <returns> + /// A <see cref="Playlist"/> objects. + /// </returns> + [HttpGet("{playlistId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<PlaylistDto> GetPlaylist( + [FromRoute, Required] Guid playlistId) + { + var userId = User.GetUserId(); + + var playlist = _playlistManager.GetPlaylistForUser(playlistId, userId); + if (playlist is null) + { + return NotFound("Playlist not found"); + } + + return new PlaylistDto() + { + Shares = playlist.Shares, + OpenAccess = playlist.OpenAccess, + ItemIds = playlist.GetManageableItems().Select(t => t.Item2.Id).ToList() + }; + } + + /// <summary> + /// Get a playlist's users. + /// </summary> + /// <param name="playlistId">The playlist id.</param> + /// <response code="200">Found shares.</response> + /// <response code="403">Access forbidden.</response> + /// <response code="404">Playlist not found.</response> + /// <returns> + /// A list of <see cref="PlaylistUserPermissions"/> objects. + /// </returns> + [HttpGet("{playlistId}/Users")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<IReadOnlyList<PlaylistUserPermissions>> GetPlaylistUsers( + [FromRoute, Required] Guid playlistId) + { + var userId = User.GetUserId(); + + var playlist = _playlistManager.GetPlaylistForUser(playlistId, userId); + if (playlist is null) + { + return NotFound("Playlist not found"); + } + + var isPermitted = playlist.OwnerUserId.Equals(userId); + + return isPermitted ? playlist.Shares.ToList() : Forbid(); + } + + /// <summary> + /// Get a playlist user. + /// </summary> + /// <param name="playlistId">The playlist id.</param> + /// <param name="userId">The user id.</param> + /// <response code="200">User permission found.</response> + /// <response code="403">Access forbidden.</response> + /// <response code="404">Playlist not found.</response> + /// <returns> + /// <see cref="PlaylistUserPermissions"/>. + /// </returns> + [HttpGet("{playlistId}/Users/{userId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<PlaylistUserPermissions?> GetPlaylistUser( + [FromRoute, Required] Guid playlistId, + [FromRoute, Required] Guid userId) + { + var callingUserId = User.GetUserId(); + + var playlist = _playlistManager.GetPlaylistForUser(playlistId, callingUserId); + if (playlist is null) + { + return NotFound("Playlist not found"); + } + + if (playlist.OwnerUserId.Equals(callingUserId)) + { + return new PlaylistUserPermissions(callingUserId, true); + } + + var userPermission = playlist.Shares.FirstOrDefault(s => s.UserId.Equals(userId)); + var isPermitted = playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId)) + || userId.Equals(callingUserId); + + if (!isPermitted) + { + return Forbid(); + } + + if (userPermission is not null) + { + return userPermission; + } + + return NotFound("User permissions not found"); + } + + /// <summary> + /// Modify a user of a playlist's users. + /// </summary> + /// <param name="playlistId">The playlist id.</param> + /// <param name="userId">The user id.</param> + /// <param name="updatePlaylistUserRequest">The <see cref="UpdatePlaylistUserDto"/>.</param> + /// <response code="204">User's permissions modified.</response> + /// <response code="403">Access forbidden.</response> + /// <response code="404">Playlist not found.</response> + /// <returns> + /// A <see cref="Task" /> that represents the asynchronous operation to modify an user's playlist permissions. + /// The task result contains an <see cref="OkResult"/> indicating success. + /// </returns> + [HttpPost("{playlistId}/Users/{userId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> UpdatePlaylistUser( + [FromRoute, Required] Guid playlistId, + [FromRoute, Required] Guid userId, + [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow), Required] UpdatePlaylistUserDto updatePlaylistUserRequest) + { + var callingUserId = User.GetUserId(); + + var playlist = _playlistManager.GetPlaylistForUser(playlistId, callingUserId); + if (playlist is null) + { + return NotFound("Playlist not found"); + } + + var isPermitted = playlist.OwnerUserId.Equals(callingUserId); + + if (!isPermitted) + { + return Forbid(); + } + + await _playlistManager.AddUserToShares(new PlaylistUserUpdateRequest + { + Id = playlistId, + UserId = userId, + CanEdit = updatePlaylistUserRequest.CanEdit + }).ConfigureAwait(false); + + return NoContent(); + } + + /// <summary> + /// Remove a user from a playlist's users. + /// </summary> + /// <param name="playlistId">The playlist id.</param> + /// <param name="userId">The user id.</param> + /// <response code="204">User permissions removed from playlist.</response> + /// <response code="401">Unauthorized access.</response> + /// <response code="404">No playlist or user permissions found.</response> + /// <returns> + /// A <see cref="Task" /> that represents the asynchronous operation to delete a user from a playlist's shares. + /// The task result contains an <see cref="OkResult"/> indicating success. + /// </returns> + [HttpDelete("{playlistId}/Users/{userId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> RemoveUserFromPlaylist( + [FromRoute, Required] Guid playlistId, + [FromRoute, Required] Guid userId) + { + var callingUserId = User.GetUserId(); + + var playlist = _playlistManager.GetPlaylistForUser(playlistId, callingUserId); + if (playlist is null) + { + return NotFound("Playlist not found"); + } + + var isPermitted = playlist.OwnerUserId.Equals(callingUserId) + || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId)); + + if (!isPermitted) + { + return Forbid(); + } + + var share = playlist.Shares.FirstOrDefault(s => s.UserId.Equals(userId)); + if (share is null) + { + return NotFound("User permissions not found"); + } + + await _playlistManager.RemoveUserFromShares(playlistId, callingUserId, share).ConfigureAwait(false); + + return NoContent(); + } + + /// <summary> + /// Adds items to a playlist. + /// </summary> + /// <param name="playlistId">The playlist id.</param> + /// <param name="ids">Item id, comma delimited.</param> + /// <param name="position">Optional. 0-based index where to place the items or at the end if <c>null</c>.</param> + /// <param name="userId">The userId.</param> + /// <response code="204">Items added to playlist.</response> + /// <response code="403">Access forbidden.</response> + /// <response code="404">Playlist not found.</response> + /// <returns>An <see cref="NoContentResult"/> on success.</returns> + [HttpPost("{playlistId}/Items")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> AddItemToPlaylist( + [FromRoute, Required] Guid playlistId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids, + [FromQuery] int? position, + [FromQuery] Guid? userId) + { + userId = RequestHelpers.GetUserId(User, userId); + var playlist = _playlistManager.GetPlaylistForUser(playlistId, userId.Value); + if (playlist is null) + { + return NotFound("Playlist not found"); + } + + var isPermitted = playlist.OwnerUserId.Equals(userId.Value) + || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(userId.Value)); + + if (!isPermitted) + { + return Forbid(); + } + + await _playlistManager.AddItemToPlaylistAsync(playlistId, ids, position, userId.Value).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Moves a playlist item. + /// </summary> + /// <param name="playlistId">The playlist id.</param> + /// <param name="itemId">The item id.</param> + /// <param name="newIndex">The new index.</param> + /// <response code="204">Item moved to new index.</response> + /// <response code="403">Access forbidden.</response> + /// <response code="404">Playlist not found.</response> + /// <returns>An <see cref="NoContentResult"/> on success.</returns> + [HttpPost("{playlistId}/Items/{itemId}/Move/{newIndex}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> MoveItem( + [FromRoute, Required] string playlistId, + [FromRoute, Required] string itemId, + [FromRoute, Required] int newIndex) + { + var callingUserId = User.GetUserId(); + + var playlist = _playlistManager.GetPlaylistForUser(Guid.Parse(playlistId), callingUserId); + if (playlist is null) + { + return NotFound("Playlist not found"); + } + + var isPermitted = playlist.OwnerUserId.Equals(callingUserId) + || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId)); + + if (!isPermitted) + { + return Forbid(); + } + + await _playlistManager.MoveItemAsync(playlistId, itemId, newIndex, callingUserId).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Removes items from a playlist. + /// </summary> + /// <param name="playlistId">The playlist id.</param> + /// <param name="entryIds">The item ids, comma delimited.</param> + /// <response code="204">Items removed.</response> + /// <response code="403">Access forbidden.</response> + /// <response code="404">Playlist not found.</response> + /// <returns>An <see cref="NoContentResult"/> on success.</returns> + [HttpDelete("{playlistId}/Items")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> RemoveItemFromPlaylist( + [FromRoute, Required] string playlistId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] entryIds) + { + var callingUserId = User.GetUserId(); + + if (!callingUserId.IsEmpty()) + { + var playlist = _playlistManager.GetPlaylistForUser(Guid.Parse(playlistId), callingUserId); + if (playlist is null) + { + return NotFound("Playlist not found"); + } + + var isPermitted = playlist.OwnerUserId.Equals(callingUserId) + || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId)); + + if (!isPermitted) + { + return Forbid(); + } + } + else + { + var isApiKey = User.GetIsApiKey(); + + if (!isApiKey) + { + return Forbid(); + } + } + + try + { + await _playlistManager.RemoveItemFromPlaylistAsync(playlistId, entryIds).ConfigureAwait(false); + return NoContent(); + } + catch (ArgumentException) + { + return NotFound(); + } + } + + /// <summary> + /// Gets the original items of a playlist. + /// </summary> + /// <param name="playlistId">The playlist id.</param> + /// <param name="userId">User id.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <response code="200">Original playlist returned.</response> + /// <response code="404">Access forbidden.</response> + /// <response code="404">Playlist not found.</response> + /// <returns>The original playlist items.</returns> + [HttpGet("{playlistId}/Items")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<QueryResult<BaseItemDto>> GetPlaylistItems( + [FromRoute, Required] Guid playlistId, + [FromQuery] Guid? userId, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] bool? enableImages, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes) + { + var callingUserId = userId ?? User.GetUserId(); + var playlist = _playlistManager.GetPlaylistForUser(playlistId, callingUserId); + if (playlist is null) + { + return NotFound("Playlist not found"); + } + + var isPermitted = playlist.OpenAccess + || playlist.OwnerUserId.Equals(callingUserId) + || playlist.Shares.Any(s => s.UserId.Equals(callingUserId)); + + if (!isPermitted) + { + return Forbid(); + } + + var user = _userManager.GetUserById(callingUserId); + var items = playlist.GetManageableItems().Where(i => i.Item2.IsVisible(user)).ToArray(); + var count = items.Length; + if (startIndex.HasValue) + { + items = items.Skip(startIndex.Value).ToArray(); + } + + if (limit.HasValue) + { + items = items.Take(limit.Value).ToArray(); + } + + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + + var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user); + for (int index = 0; index < dtos.Count; index++) + { + dtos[index].PlaylistItemId = items[index].Item1.ItemId?.ToString("N", CultureInfo.InvariantCulture); + } + + var result = new QueryResult<BaseItemDto>( + startIndex, + count, + dtos); + + return result; + } +} diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs new file mode 100644 index 00000000..ade0906b --- /dev/null +++ b/Jellyfin.Api/Controllers/PlaystateController.cs @@ -0,0 +1,535 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Session; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Playstate controller. +/// </summary> +[Route("")] +[Authorize] +public class PlaystateController : BaseJellyfinApiController +{ + private readonly IUserManager _userManager; + private readonly IUserDataManager _userDataRepository; + private readonly ILibraryManager _libraryManager; + private readonly ISessionManager _sessionManager; + private readonly ILogger<PlaystateController> _logger; + private readonly ITranscodeManager _transcodeManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PlaystateController"/> class. + /// </summary> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="userDataRepository">Instance of the <see cref="IUserDataManager"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param> + /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param> + /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param> + public PlaystateController( + IUserManager userManager, + IUserDataManager userDataRepository, + ILibraryManager libraryManager, + ISessionManager sessionManager, + ILoggerFactory loggerFactory, + ITranscodeManager transcodeManager) + { + _userManager = userManager; + _userDataRepository = userDataRepository; + _libraryManager = libraryManager; + _sessionManager = sessionManager; + _logger = loggerFactory.CreateLogger<PlaystateController>(); + + _transcodeManager = transcodeManager; + } + + /// <summary> + /// Marks an item as played for user. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <param name="datePlayed">Optional. The date the item was played.</param> + /// <response code="200">Item marked as played.</response> + /// <response code="404">Item not found.</response> + /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>, or a <see cref="NotFoundResult"/> if item was not found.</returns> + [HttpPost("UserPlayedItems/{itemId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult<UserItemDataDto?>> MarkPlayedItem( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId, + [FromQuery, ModelBinder(typeof(LegacyDateTimeModelBinder))] DateTime? datePlayed) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(userId.Value); + if (user is null) + { + return NotFound(); + } + + var item = _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext, userId).ConfigureAwait(false); + + var dto = UpdatePlayedStatus(user, item, true, datePlayed); + foreach (var additionalUserInfo in session.AdditionalUsers) + { + var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId); + if (additionalUser is null) + { + return NotFound(); + } + + UpdatePlayedStatus(additionalUser, item, true, datePlayed); + } + + return dto; + } + + /// <summary> + /// Marks an item as played for user. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <param name="datePlayed">Optional. The date the item was played.</param> + /// <response code="200">Item marked as played.</response> + /// <response code="404">Item not found.</response> + /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>, or a <see cref="NotFoundResult"/> if item was not found.</returns> + [HttpPost("Users/{userId}/PlayedItems/{itemId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public Task<ActionResult<UserItemDataDto?>> MarkPlayedItemLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId, + [FromQuery, ModelBinder(typeof(LegacyDateTimeModelBinder))] DateTime? datePlayed) + => MarkPlayedItem(userId, itemId, datePlayed); + + /// <summary> + /// Marks an item as unplayed for user. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <response code="200">Item marked as unplayed.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>, or a <see cref="NotFoundResult"/> if item was not found.</returns> + [HttpDelete("UserPlayedItems/{itemId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult<UserItemDataDto?>> MarkUnplayedItem( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(userId.Value); + if (user is null) + { + return NotFound(); + } + + var item = _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext, userId).ConfigureAwait(false); + + var dto = UpdatePlayedStatus(user, item, false, null); + foreach (var additionalUserInfo in session.AdditionalUsers) + { + var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId); + if (additionalUser is null) + { + return NotFound(); + } + + UpdatePlayedStatus(additionalUser, item, false, null); + } + + return dto; + } + + /// <summary> + /// Marks an item as unplayed for user. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <response code="200">Item marked as unplayed.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>, or a <see cref="NotFoundResult"/> if item was not found.</returns> + [HttpDelete("Users/{userId}/PlayedItems/{itemId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public Task<ActionResult<UserItemDataDto?>> MarkUnplayedItemLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId) + => MarkUnplayedItem(userId, itemId); + + /// <summary> + /// Reports playback has started within a session. + /// </summary> + /// <param name="playbackStartInfo">The playback start info.</param> + /// <response code="204">Playback start recorded.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Sessions/Playing")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> ReportPlaybackStart([FromBody] PlaybackStartInfo playbackStartInfo) + { + playbackStartInfo.PlayMethod = ValidatePlayMethod(playbackStartInfo.PlayMethod, playbackStartInfo.PlaySessionId); + playbackStartInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + await _sessionManager.OnPlaybackStart(playbackStartInfo).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Reports playback progress within a session. + /// </summary> + /// <param name="playbackProgressInfo">The playback progress info.</param> + /// <response code="204">Playback progress recorded.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Sessions/Playing/Progress")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> ReportPlaybackProgress([FromBody] PlaybackProgressInfo playbackProgressInfo) + { + playbackProgressInfo.PlayMethod = ValidatePlayMethod(playbackProgressInfo.PlayMethod, playbackProgressInfo.PlaySessionId); + playbackProgressInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + await _sessionManager.OnPlaybackProgress(playbackProgressInfo).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Pings a playback session. + /// </summary> + /// <param name="playSessionId">Playback session id.</param> + /// <response code="204">Playback session pinged.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Sessions/Playing/Ping")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult PingPlaybackSession([FromQuery, Required] string playSessionId) + { + _transcodeManager.PingTranscodingJob(playSessionId, null); + return NoContent(); + } + + /// <summary> + /// Reports playback has stopped within a session. + /// </summary> + /// <param name="playbackStopInfo">The playback stop info.</param> + /// <response code="204">Playback stop recorded.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Sessions/Playing/Stopped")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> ReportPlaybackStopped([FromBody] PlaybackStopInfo playbackStopInfo) + { + _logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty); + if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId)) + { + await _transcodeManager.KillTranscodingJobs(User.GetDeviceId()!, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); + } + + playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + await _sessionManager.OnPlaybackStopped(playbackStopInfo).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Reports that a session has begun playing an item. + /// </summary> + /// <param name="itemId">Item id.</param> + /// <param name="mediaSourceId">The id of the MediaSource.</param> + /// <param name="audioStreamIndex">The audio stream index.</param> + /// <param name="subtitleStreamIndex">The subtitle stream index.</param> + /// <param name="playMethod">The play method.</param> + /// <param name="liveStreamId">The live stream id.</param> + /// <param name="playSessionId">The play session id.</param> + /// <param name="canSeek">Indicates if the client can seek.</param> + /// <response code="204">Play start recorded.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("PlayingItems/{itemId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Obsolete("This endpoint is obsolete. Use ReportPlaybackStart instead")] + public async Task<ActionResult> OnPlaybackStart( + [FromRoute, Required] Guid itemId, + [FromQuery] string? mediaSourceId, + [FromQuery] int? audioStreamIndex, + [FromQuery] int? subtitleStreamIndex, + [FromQuery] PlayMethod? playMethod, + [FromQuery] string? liveStreamId, + [FromQuery] string? playSessionId, + [FromQuery] bool canSeek = false) + { + var playbackStartInfo = new PlaybackStartInfo + { + CanSeek = canSeek, + ItemId = itemId, + MediaSourceId = mediaSourceId, + AudioStreamIndex = audioStreamIndex, + SubtitleStreamIndex = subtitleStreamIndex, + PlayMethod = playMethod ?? PlayMethod.Transcode, + PlaySessionId = playSessionId, + LiveStreamId = liveStreamId + }; + + playbackStartInfo.PlayMethod = ValidatePlayMethod(playbackStartInfo.PlayMethod, playbackStartInfo.PlaySessionId); + playbackStartInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + await _sessionManager.OnPlaybackStart(playbackStartInfo).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Reports that a user has begun playing an item. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <param name="mediaSourceId">The id of the MediaSource.</param> + /// <param name="audioStreamIndex">The audio stream index.</param> + /// <param name="subtitleStreamIndex">The subtitle stream index.</param> + /// <param name="playMethod">The play method.</param> + /// <param name="liveStreamId">The live stream id.</param> + /// <param name="playSessionId">The play session id.</param> + /// <param name="canSeek">Indicates if the client can seek.</param> + /// <response code="204">Play start recorded.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Users/{userId}/PlayingItems/{itemId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")] + public Task<ActionResult> OnPlaybackStartLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId, + [FromQuery] string? mediaSourceId, + [FromQuery] int? audioStreamIndex, + [FromQuery] int? subtitleStreamIndex, + [FromQuery] PlayMethod? playMethod, + [FromQuery] string? liveStreamId, + [FromQuery] string? playSessionId, + [FromQuery] bool canSeek = false) + => OnPlaybackStart(itemId, mediaSourceId, audioStreamIndex, subtitleStreamIndex, playMethod, liveStreamId, playSessionId, canSeek); + + /// <summary> + /// Reports a session's playback progress. + /// </summary> + /// <param name="itemId">Item id.</param> + /// <param name="mediaSourceId">The id of the MediaSource.</param> + /// <param name="positionTicks">Optional. The current position, in ticks. 1 tick = 10000 ms.</param> + /// <param name="audioStreamIndex">The audio stream index.</param> + /// <param name="subtitleStreamIndex">The subtitle stream index.</param> + /// <param name="volumeLevel">Scale of 0-100.</param> + /// <param name="playMethod">The play method.</param> + /// <param name="liveStreamId">The live stream id.</param> + /// <param name="playSessionId">The play session id.</param> + /// <param name="repeatMode">The repeat mode.</param> + /// <param name="isPaused">Indicates if the player is paused.</param> + /// <param name="isMuted">Indicates if the player is muted.</param> + /// <response code="204">Play progress recorded.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("PlayingItems/{itemId}/Progress")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Obsolete("This endpoint is obsolete. Use ReportPlaybackProgress instead")] + public async Task<ActionResult> OnPlaybackProgress( + [FromRoute, Required] Guid itemId, + [FromQuery] string? mediaSourceId, + [FromQuery] long? positionTicks, + [FromQuery] int? audioStreamIndex, + [FromQuery] int? subtitleStreamIndex, + [FromQuery] int? volumeLevel, + [FromQuery] PlayMethod? playMethod, + [FromQuery] string? liveStreamId, + [FromQuery] string? playSessionId, + [FromQuery] RepeatMode? repeatMode, + [FromQuery] bool isPaused = false, + [FromQuery] bool isMuted = false) + { + var playbackProgressInfo = new PlaybackProgressInfo + { + ItemId = itemId, + PositionTicks = positionTicks, + IsMuted = isMuted, + IsPaused = isPaused, + MediaSourceId = mediaSourceId, + AudioStreamIndex = audioStreamIndex, + SubtitleStreamIndex = subtitleStreamIndex, + VolumeLevel = volumeLevel, + PlayMethod = playMethod ?? PlayMethod.Transcode, + PlaySessionId = playSessionId, + LiveStreamId = liveStreamId, + RepeatMode = repeatMode ?? RepeatMode.RepeatNone + }; + + playbackProgressInfo.PlayMethod = ValidatePlayMethod(playbackProgressInfo.PlayMethod, playbackProgressInfo.PlaySessionId); + playbackProgressInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + await _sessionManager.OnPlaybackProgress(playbackProgressInfo).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Reports a user's playback progress. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <param name="mediaSourceId">The id of the MediaSource.</param> + /// <param name="positionTicks">Optional. The current position, in ticks. 1 tick = 10000 ms.</param> + /// <param name="audioStreamIndex">The audio stream index.</param> + /// <param name="subtitleStreamIndex">The subtitle stream index.</param> + /// <param name="volumeLevel">Scale of 0-100.</param> + /// <param name="playMethod">The play method.</param> + /// <param name="liveStreamId">The live stream id.</param> + /// <param name="playSessionId">The play session id.</param> + /// <param name="repeatMode">The repeat mode.</param> + /// <param name="isPaused">Indicates if the player is paused.</param> + /// <param name="isMuted">Indicates if the player is muted.</param> + /// <response code="204">Play progress recorded.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Users/{userId}/PlayingItems/{itemId}/Progress")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")] + public Task<ActionResult> OnPlaybackProgressLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId, + [FromQuery] string? mediaSourceId, + [FromQuery] long? positionTicks, + [FromQuery] int? audioStreamIndex, + [FromQuery] int? subtitleStreamIndex, + [FromQuery] int? volumeLevel, + [FromQuery] PlayMethod? playMethod, + [FromQuery] string? liveStreamId, + [FromQuery] string? playSessionId, + [FromQuery] RepeatMode? repeatMode, + [FromQuery] bool isPaused = false, + [FromQuery] bool isMuted = false) + => OnPlaybackProgress(itemId, mediaSourceId, positionTicks, audioStreamIndex, subtitleStreamIndex, volumeLevel, playMethod, liveStreamId, playSessionId, repeatMode, isPaused, isMuted); + + /// <summary> + /// Reports that a session has stopped playing an item. + /// </summary> + /// <param name="itemId">Item id.</param> + /// <param name="mediaSourceId">The id of the MediaSource.</param> + /// <param name="nextMediaType">The next media type that will play.</param> + /// <param name="positionTicks">Optional. The position, in ticks, where playback stopped. 1 tick = 10000 ms.</param> + /// <param name="liveStreamId">The live stream id.</param> + /// <param name="playSessionId">The play session id.</param> + /// <response code="204">Playback stop recorded.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpDelete("PlayingItems/{itemId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Obsolete("This endpoint is obsolete. Use ReportPlaybackStop instead")] + public async Task<ActionResult> OnPlaybackStopped( + [FromRoute, Required] Guid itemId, + [FromQuery] string? mediaSourceId, + [FromQuery] string? nextMediaType, + [FromQuery] long? positionTicks, + [FromQuery] string? liveStreamId, + [FromQuery] string? playSessionId) + { + var playbackStopInfo = new PlaybackStopInfo + { + ItemId = itemId, + PositionTicks = positionTicks, + MediaSourceId = mediaSourceId, + PlaySessionId = playSessionId, + LiveStreamId = liveStreamId, + NextMediaType = nextMediaType + }; + + _logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty); + if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId)) + { + await _transcodeManager.KillTranscodingJobs(User.GetDeviceId()!, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); + } + + playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + await _sessionManager.OnPlaybackStopped(playbackStopInfo).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Reports that a user has stopped playing an item. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <param name="mediaSourceId">The id of the MediaSource.</param> + /// <param name="nextMediaType">The next media type that will play.</param> + /// <param name="positionTicks">Optional. The position, in ticks, where playback stopped. 1 tick = 10000 ms.</param> + /// <param name="liveStreamId">The live stream id.</param> + /// <param name="playSessionId">The play session id.</param> + /// <response code="204">Playback stop recorded.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpDelete("Users/{userId}/PlayingItems/{itemId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")] + public Task<ActionResult> OnPlaybackStoppedLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId, + [FromQuery] string? mediaSourceId, + [FromQuery] string? nextMediaType, + [FromQuery] long? positionTicks, + [FromQuery] string? liveStreamId, + [FromQuery] string? playSessionId) + => OnPlaybackStopped(itemId, mediaSourceId, nextMediaType, positionTicks, liveStreamId, playSessionId); + + /// <summary> + /// Updates the played status. + /// </summary> + /// <param name="user">The user.</param> + /// <param name="item">The item.</param> + /// <param name="wasPlayed">if set to <c>true</c> [was played].</param> + /// <param name="datePlayed">The date played.</param> + /// <returns>Task.</returns> + private UserItemDataDto? UpdatePlayedStatus(User user, BaseItem item, bool wasPlayed, DateTime? datePlayed) + { + if (wasPlayed) + { + item.MarkPlayed(user, datePlayed, true); + } + else + { + item.MarkUnplayed(user); + } + + return _userDataRepository.GetUserDataDto(item, user); + } + + private PlayMethod ValidatePlayMethod(PlayMethod method, string? playSessionId) + { + if (method == PlayMethod.Transcode) + { + var job = string.IsNullOrWhiteSpace(playSessionId) ? null : _transcodeManager.GetTranscodingJob(playSessionId); + if (job is null) + { + return PlayMethod.DirectPlay; + } + } + + return method; + } +} diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs new file mode 100644 index 00000000..53b7349e --- /dev/null +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -0,0 +1,263 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Jellyfin.Api.Attributes; +using Jellyfin.Api.Constants; +using Jellyfin.Extensions.Json; +using MediaBrowser.Common.Api; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Common.Updates; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Plugins; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Plugins controller. +/// </summary> +[Authorize(Policy = Policies.RequiresElevation)] +public class PluginsController : BaseJellyfinApiController +{ + private readonly IInstallationManager _installationManager; + private readonly IPluginManager _pluginManager; + private readonly JsonSerializerOptions _serializerOptions; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginsController"/> class. + /// </summary> + /// <param name="installationManager">Instance of the <see cref="IInstallationManager"/> interface.</param> + /// <param name="pluginManager">Instance of the <see cref="IPluginManager"/> interface.</param> + public PluginsController( + IInstallationManager installationManager, + IPluginManager pluginManager) + { + _installationManager = installationManager; + _pluginManager = pluginManager; + _serializerOptions = JsonDefaults.Options; + } + + /// <summary> + /// Gets a list of currently installed plugins. + /// </summary> + /// <response code="200">Installed plugins returned.</response> + /// <returns>List of currently installed plugins.</returns> + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<IEnumerable<PluginInfo>> GetPlugins() + { + return Ok(_pluginManager.Plugins + .OrderBy(p => p.Name) + .Select(p => p.GetPluginInfo())); + } + + /// <summary> + /// Enables a disabled plugin. + /// </summary> + /// <param name="pluginId">Plugin id.</param> + /// <param name="version">Plugin version.</param> + /// <response code="204">Plugin enabled.</response> + /// <response code="404">Plugin not found.</response> + /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the plugin could not be found.</returns> + [HttpPost("{pluginId}/{version}/Enable")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult EnablePlugin([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version) + { + var plugin = _pluginManager.GetPlugin(pluginId, version); + if (plugin is null) + { + return NotFound(); + } + + _pluginManager.EnablePlugin(plugin); + return NoContent(); + } + + /// <summary> + /// Disable a plugin. + /// </summary> + /// <param name="pluginId">Plugin id.</param> + /// <param name="version">Plugin version.</param> + /// <response code="204">Plugin disabled.</response> + /// <response code="404">Plugin not found.</response> + /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the plugin could not be found.</returns> + [HttpPost("{pluginId}/{version}/Disable")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult DisablePlugin([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version) + { + var plugin = _pluginManager.GetPlugin(pluginId, version); + if (plugin is null) + { + return NotFound(); + } + + _pluginManager.DisablePlugin(plugin); + return NoContent(); + } + + /// <summary> + /// Uninstalls a plugin by version. + /// </summary> + /// <param name="pluginId">Plugin id.</param> + /// <param name="version">Plugin version.</param> + /// <response code="204">Plugin uninstalled.</response> + /// <response code="404">Plugin not found.</response> + /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the plugin could not be found.</returns> + [HttpDelete("{pluginId}/{version}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult UninstallPluginByVersion([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version) + { + var plugin = _pluginManager.GetPlugin(pluginId, version); + if (plugin is null) + { + return NotFound(); + } + + _installationManager.UninstallPlugin(plugin); + return NoContent(); + } + + /// <summary> + /// Uninstalls a plugin. + /// </summary> + /// <param name="pluginId">Plugin id.</param> + /// <response code="204">Plugin uninstalled.</response> + /// <response code="404">Plugin not found.</response> + /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the plugin could not be found.</returns> + [HttpDelete("{pluginId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Obsolete("Please use the UninstallPluginByVersion API.")] + public ActionResult UninstallPlugin([FromRoute, Required] Guid pluginId) + { + // If no version is given, return the current instance. + var plugins = _pluginManager.Plugins.Where(p => p.Id.Equals(pluginId)).ToList(); + + // Select the un-instanced one first. + var plugin = plugins.FirstOrDefault(p => p.Instance is null) ?? plugins.MinBy(p => p.Manifest.Status); + + if (plugin is not null) + { + _installationManager.UninstallPlugin(plugin); + return NoContent(); + } + + return NotFound(); + } + + /// <summary> + /// Gets plugin configuration. + /// </summary> + /// <param name="pluginId">Plugin id.</param> + /// <response code="200">Plugin configuration returned.</response> + /// <response code="404">Plugin not found or plugin configuration not found.</response> + /// <returns>Plugin configuration.</returns> + [HttpGet("{pluginId}/Configuration")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<BasePluginConfiguration> GetPluginConfiguration([FromRoute, Required] Guid pluginId) + { + var plugin = _pluginManager.GetPlugin(pluginId); + if (plugin?.Instance is IHasPluginConfiguration configPlugin) + { + return configPlugin.Configuration; + } + + return NotFound(); + } + + /// <summary> + /// Updates plugin configuration. + /// </summary> + /// <remarks> + /// Accepts plugin configuration as JSON body. + /// </remarks> + /// <param name="pluginId">Plugin id.</param> + /// <response code="204">Plugin configuration updated.</response> + /// <response code="404">Plugin not found or plugin does not have configuration.</response> + /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the plugin could not be found.</returns> + [HttpPost("{pluginId}/Configuration")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> UpdatePluginConfiguration([FromRoute, Required] Guid pluginId) + { + var plugin = _pluginManager.GetPlugin(pluginId); + if (plugin?.Instance is not IHasPluginConfiguration configPlugin) + { + return NotFound(); + } + + var configuration = (BasePluginConfiguration?)await JsonSerializer.DeserializeAsync(Request.Body, configPlugin.ConfigurationType, _serializerOptions) + .ConfigureAwait(false); + + if (configuration is not null) + { + configPlugin.UpdateConfiguration(configuration); + } + + return NoContent(); + } + + /// <summary> + /// Gets a plugin's image. + /// </summary> + /// <param name="pluginId">Plugin id.</param> + /// <param name="version">Plugin version.</param> + /// <response code="200">Plugin image returned.</response> + /// <returns>Plugin's image.</returns> + [HttpGet("{pluginId}/{version}/Image")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + [AllowAnonymous] + public ActionResult GetPluginImage([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version) + { + var plugin = _pluginManager.GetPlugin(pluginId, version); + if (plugin is null) + { + return NotFound(); + } + + var imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath ?? string.Empty); + if (plugin.Manifest.ImagePath is null || !System.IO.File.Exists(imagePath)) + { + return NotFound(); + } + + Response.Headers.ContentDisposition = "attachment"; + + imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath); + return PhysicalFile(imagePath, MimeTypes.GetMimeType(imagePath)); + } + + /// <summary> + /// Gets a plugin's manifest. + /// </summary> + /// <param name="pluginId">Plugin id.</param> + /// <response code="204">Plugin manifest returned.</response> + /// <response code="404">Plugin not found.</response> + /// <returns>A <see cref="PluginManifest"/> on success, or a <see cref="NotFoundResult"/> if the plugin could not be found.</returns> + [HttpPost("{pluginId}/Manifest")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<PluginManifest> GetPluginManifest([FromRoute, Required] Guid pluginId) + { + var plugin = _pluginManager.GetPlugin(pluginId); + + if (plugin is not null) + { + return plugin.Manifest; + } + + return NotFound(); + } +} diff --git a/Jellyfin.Api/Controllers/QuickConnectController.cs b/Jellyfin.Api/Controllers/QuickConnectController.cs new file mode 100644 index 00000000..2a15ff76 --- /dev/null +++ b/Jellyfin.Api/Controllers/QuickConnectController.cs @@ -0,0 +1,119 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Jellyfin.Api.Helpers; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.QuickConnect; +using MediaBrowser.Model.QuickConnect; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Quick connect controller. +/// </summary> +public class QuickConnectController : BaseJellyfinApiController +{ + private readonly IQuickConnect _quickConnect; + private readonly IAuthorizationContext _authContext; + + /// <summary> + /// Initializes a new instance of the <see cref="QuickConnectController"/> class. + /// </summary> + /// <param name="quickConnect">Instance of the <see cref="IQuickConnect"/> interface.</param> + /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param> + public QuickConnectController(IQuickConnect quickConnect, IAuthorizationContext authContext) + { + _quickConnect = quickConnect; + _authContext = authContext; + } + + /// <summary> + /// Gets the current quick connect state. + /// </summary> + /// <response code="200">Quick connect state returned.</response> + /// <returns>Whether Quick Connect is enabled on the server or not.</returns> + [HttpGet("Enabled")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<bool> GetQuickConnectEnabled() + { + return _quickConnect.IsEnabled; + } + + /// <summary> + /// Initiate a new quick connect request. + /// </summary> + /// <response code="200">Quick connect request successfully created.</response> + /// <response code="401">Quick connect is not active on this server.</response> + /// <returns>A <see cref="QuickConnectResult"/> with a secret and code for future use or an error message.</returns> + [HttpPost("Initiate")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<QuickConnectResult>> InitiateQuickConnect() + { + try + { + var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); + return _quickConnect.TryConnect(auth); + } + catch (AuthenticationException) + { + return Unauthorized("Quick connect is disabled"); + } + } + + /// <summary> + /// Attempts to retrieve authentication information. + /// </summary> + /// <param name="secret">Secret previously returned from the Initiate endpoint.</param> + /// <response code="200">Quick connect result returned.</response> + /// <response code="404">Unknown quick connect secret.</response> + /// <returns>An updated <see cref="QuickConnectResult"/>.</returns> + [HttpGet("Connect")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<QuickConnectResult> GetQuickConnectState([FromQuery, Required] string secret) + { + try + { + return _quickConnect.CheckRequestStatus(secret); + } + catch (ResourceNotFoundException) + { + return NotFound("Unknown secret"); + } + catch (AuthenticationException) + { + return Unauthorized("Quick connect is disabled"); + } + } + + /// <summary> + /// Authorizes a pending quick connect request. + /// </summary> + /// <param name="code">Quick connect code to authorize.</param> + /// <param name="userId">The user the authorize. Access to the requested user is required.</param> + /// <response code="200">Quick connect result authorized successfully.</response> + /// <response code="403">Unknown user id.</response> + /// <returns>Boolean indicating if the authorization was successful.</returns> + [HttpPost("Authorize")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task<ActionResult<bool>> AuthorizeQuickConnect([FromQuery, Required] string code, [FromQuery] Guid? userId = null) + { + userId = RequestHelpers.GetUserId(User, userId); + + try + { + return await _quickConnect.AuthorizeRequest(userId.Value, code).ConfigureAwait(false); + } + catch (AuthenticationException) + { + return Unauthorized("Quick connect is disabled"); + } + } +} diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs new file mode 100644 index 00000000..a476005c --- /dev/null +++ b/Jellyfin.Api/Controllers/RemoteImageController.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using MediaBrowser.Common.Api; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Remote Images Controller. +/// </summary> +[Route("")] +public class RemoteImageController : BaseJellyfinApiController +{ + private readonly IProviderManager _providerManager; + private readonly IServerApplicationPaths _applicationPaths; + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// Initializes a new instance of the <see cref="RemoteImageController"/> class. + /// </summary> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + public RemoteImageController( + IProviderManager providerManager, + IServerApplicationPaths applicationPaths, + ILibraryManager libraryManager) + { + _providerManager = providerManager; + _applicationPaths = applicationPaths; + _libraryManager = libraryManager; + } + + /// <summary> + /// Gets available remote images for an item. + /// </summary> + /// <param name="itemId">Item Id.</param> + /// <param name="type">The image type.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="providerName">Optional. The image provider to use.</param> + /// <param name="includeAllLanguages">Optional. Include all languages.</param> + /// <response code="200">Remote Images returned.</response> + /// <response code="404">Item not found.</response> + /// <returns>Remote Image Result.</returns> + [HttpGet("Items/{itemId}/RemoteImages")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult<RemoteImageResult>> GetRemoteImages( + [FromRoute, Required] Guid itemId, + [FromQuery] ImageType? type, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] string? providerName, + [FromQuery] bool includeAllLanguages = false) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + var images = await _providerManager.GetAvailableRemoteImages( + item, + new RemoteImageQuery(providerName ?? string.Empty) + { + IncludeAllLanguages = includeAllLanguages, + IncludeDisabledProviders = true, + ImageType = type + }, + CancellationToken.None) + .ConfigureAwait(false); + + var imageArray = images.ToArray(); + var allProviders = _providerManager.GetRemoteImageProviderInfo(item); + if (type.HasValue) + { + allProviders = allProviders.Where(o => o.SupportedImages.Contains(type.Value)); + } + + var result = new RemoteImageResult + { + TotalRecordCount = imageArray.Length, + Providers = allProviders.Select(o => o.Name) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray() + }; + + if (startIndex.HasValue) + { + imageArray = imageArray.Skip(startIndex.Value).ToArray(); + } + + if (limit.HasValue) + { + imageArray = imageArray.Take(limit.Value).ToArray(); + } + + result.Images = imageArray; + return result; + } + + /// <summary> + /// Gets available remote image providers for an item. + /// </summary> + /// <param name="itemId">Item Id.</param> + /// <response code="200">Returned remote image providers.</response> + /// <response code="404">Item not found.</response> + /// <returns>List of remote image providers.</returns> + [HttpGet("Items/{itemId}/RemoteImages/Providers")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<IEnumerable<ImageProviderInfo>> GetRemoteImageProviders([FromRoute, Required] Guid itemId) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + return Ok(_providerManager.GetRemoteImageProviderInfo(item)); + } + + /// <summary> + /// Downloads a remote image for an item. + /// </summary> + /// <param name="itemId">Item Id.</param> + /// <param name="type">The image type.</param> + /// <param name="imageUrl">The image url.</param> + /// <response code="204">Remote image downloaded.</response> + /// <response code="404">Remote image not found.</response> + /// <returns>Download status.</returns> + [HttpPost("Items/{itemId}/RemoteImages/Download")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> DownloadRemoteImage( + [FromRoute, Required] Guid itemId, + [FromQuery, Required] ImageType type, + [FromQuery] string? imageUrl) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + await _providerManager.SaveImage(item, imageUrl, type, null, CancellationToken.None) + .ConfigureAwait(false); + + await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Gets the full cache path. + /// </summary> + /// <param name="filename">The filename.</param> + /// <returns>System.String.</returns> + private string GetFullCachePath(string filename) + { + return Path.Combine(_applicationPaths.CachePath, "remote-images", filename.Substring(0, 1), filename); + } +} diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs new file mode 100644 index 00000000..065466cb --- /dev/null +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Jellyfin.Api.Constants; +using MediaBrowser.Common.Api; +using MediaBrowser.Model.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Scheduled Tasks Controller. +/// </summary> +[Authorize(Policy = Policies.RequiresElevation)] +public class ScheduledTasksController : BaseJellyfinApiController +{ + private readonly ITaskManager _taskManager; + + /// <summary> + /// Initializes a new instance of the <see cref="ScheduledTasksController"/> class. + /// </summary> + /// <param name="taskManager">Instance of the <see cref="ITaskManager"/> interface.</param> + public ScheduledTasksController(ITaskManager taskManager) + { + _taskManager = taskManager; + } + + /// <summary> + /// Get tasks. + /// </summary> + /// <param name="isHidden">Optional filter tasks that are hidden, or not.</param> + /// <param name="isEnabled">Optional filter tasks that are enabled, or not.</param> + /// <response code="200">Scheduled tasks retrieved.</response> + /// <returns>The list of scheduled tasks.</returns> + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public IEnumerable<TaskInfo> GetTasks( + [FromQuery] bool? isHidden, + [FromQuery] bool? isEnabled) + { + IEnumerable<IScheduledTaskWorker> tasks = _taskManager.ScheduledTasks.OrderBy(o => o.Name); + + foreach (var task in tasks) + { + if (task.ScheduledTask is IConfigurableScheduledTask scheduledTask) + { + if (isHidden.HasValue && isHidden.Value != scheduledTask.IsHidden) + { + continue; + } + + if (isEnabled.HasValue && isEnabled.Value != scheduledTask.IsEnabled) + { + continue; + } + } + + yield return ScheduledTaskHelpers.GetTaskInfo(task); + } + } + + /// <summary> + /// Get task by id. + /// </summary> + /// <param name="taskId">Task Id.</param> + /// <response code="200">Task retrieved.</response> + /// <response code="404">Task not found.</response> + /// <returns>An <see cref="OkResult"/> containing the task on success, or a <see cref="NotFoundResult"/> if the task could not be found.</returns> + [HttpGet("{taskId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<TaskInfo> GetTask([FromRoute, Required] string taskId) + { + var task = _taskManager.ScheduledTasks.FirstOrDefault(i => + string.Equals(i.Id, taskId, StringComparison.OrdinalIgnoreCase)); + + if (task is null) + { + return NotFound(); + } + + return ScheduledTaskHelpers.GetTaskInfo(task); + } + + /// <summary> + /// Start specified task. + /// </summary> + /// <param name="taskId">Task Id.</param> + /// <response code="204">Task started.</response> + /// <response code="404">Task not found.</response> + /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the file could not be found.</returns> + [HttpPost("Running/{taskId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult StartTask([FromRoute, Required] string taskId) + { + var task = _taskManager.ScheduledTasks.FirstOrDefault(o => + o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); + + if (task is null) + { + return NotFound(); + } + + _taskManager.Execute(task, new TaskOptions()); + return NoContent(); + } + + /// <summary> + /// Stop specified task. + /// </summary> + /// <param name="taskId">Task Id.</param> + /// <response code="204">Task stopped.</response> + /// <response code="404">Task not found.</response> + /// <returns>An <see cref="OkResult"/> on success, or a <see cref="NotFoundResult"/> if the file could not be found.</returns> + [HttpDelete("Running/{taskId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult StopTask([FromRoute, Required] string taskId) + { + var task = _taskManager.ScheduledTasks.FirstOrDefault(o => + o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); + + if (task is null) + { + return NotFound(); + } + + _taskManager.Cancel(task); + return NoContent(); + } + + /// <summary> + /// Update specified task triggers. + /// </summary> + /// <param name="taskId">Task Id.</param> + /// <param name="triggerInfos">Triggers.</param> + /// <response code="204">Task triggers updated.</response> + /// <response code="404">Task not found.</response> + /// <returns>An <see cref="OkResult"/> on success, or a <see cref="NotFoundResult"/> if the file could not be found.</returns> + [HttpPost("{taskId}/Triggers")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult UpdateTask( + [FromRoute, Required] string taskId, + [FromBody, Required] TaskTriggerInfo[] triggerInfos) + { + var task = _taskManager.ScheduledTasks.FirstOrDefault(o => + o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); + if (task is null) + { + return NotFound(); + } + + task.Triggers = triggerInfos; + return NoContent(); + } +} diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs new file mode 100644 index 00000000..ecf2335b --- /dev/null +++ b/Jellyfin.Api/Controllers/SearchController.cs @@ -0,0 +1,266 @@ +using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Linq; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Search; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Search controller. +/// </summary> +[Route("Search/Hints")] +[Authorize] +public class SearchController : BaseJellyfinApiController +{ + private readonly ISearchEngine _searchEngine; + private readonly ILibraryManager _libraryManager; + private readonly IDtoService _dtoService; + private readonly IImageProcessor _imageProcessor; + + /// <summary> + /// Initializes a new instance of the <see cref="SearchController"/> class. + /// </summary> + /// <param name="searchEngine">Instance of <see cref="ISearchEngine"/> interface.</param> + /// <param name="libraryManager">Instance of <see cref="ILibraryManager"/> interface.</param> + /// <param name="dtoService">Instance of <see cref="IDtoService"/> interface.</param> + /// <param name="imageProcessor">Instance of <see cref="IImageProcessor"/> interface.</param> + public SearchController( + ISearchEngine searchEngine, + ILibraryManager libraryManager, + IDtoService dtoService, + IImageProcessor imageProcessor) + { + _searchEngine = searchEngine; + _libraryManager = libraryManager; + _dtoService = dtoService; + _imageProcessor = imageProcessor; + } + + /// <summary> + /// Gets the search hint result. + /// </summary> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="userId">Optional. Supply a user id to search within a user's library or omit to search all.</param> + /// <param name="searchTerm">The search term to filter on.</param> + /// <param name="includeItemTypes">If specified, only results with the specified item types are returned. This allows multiple, comma delimited.</param> + /// <param name="excludeItemTypes">If specified, results with these item types are filtered out. This allows multiple, comma delimited.</param> + /// <param name="mediaTypes">If specified, only results with the specified media types are returned. This allows multiple, comma delimited.</param> + /// <param name="parentId">If specified, only children of the parent are returned.</param> + /// <param name="isMovie">Optional filter for movies.</param> + /// <param name="isSeries">Optional filter for series.</param> + /// <param name="isNews">Optional filter for news.</param> + /// <param name="isKids">Optional filter for kids.</param> + /// <param name="isSports">Optional filter for sports.</param> + /// <param name="includePeople">Optional filter whether to include people.</param> + /// <param name="includeMedia">Optional filter whether to include media.</param> + /// <param name="includeGenres">Optional filter whether to include genres.</param> + /// <param name="includeStudios">Optional filter whether to include studios.</param> + /// <param name="includeArtists">Optional filter whether to include artists.</param> + /// <response code="200">Search hint returned.</response> + /// <returns>An <see cref="SearchHintResult"/> with the results of the search.</returns> + [HttpGet] + [Description("Gets search hints based on a search term")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<SearchHintResult> GetSearchHints( + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] Guid? userId, + [FromQuery, Required] string searchTerm, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes, + [FromQuery] Guid? parentId, + [FromQuery] bool? isMovie, + [FromQuery] bool? isSeries, + [FromQuery] bool? isNews, + [FromQuery] bool? isKids, + [FromQuery] bool? isSports, + [FromQuery] bool includePeople = true, + [FromQuery] bool includeMedia = true, + [FromQuery] bool includeGenres = true, + [FromQuery] bool includeStudios = true, + [FromQuery] bool includeArtists = true) + { + userId = RequestHelpers.GetUserId(User, userId); + var result = _searchEngine.GetSearchHints(new SearchQuery + { + Limit = limit, + SearchTerm = searchTerm, + IncludeArtists = includeArtists, + IncludeGenres = includeGenres, + IncludeMedia = includeMedia, + IncludePeople = includePeople, + IncludeStudios = includeStudios, + StartIndex = startIndex, + UserId = userId.Value, + IncludeItemTypes = includeItemTypes, + ExcludeItemTypes = excludeItemTypes, + MediaTypes = mediaTypes, + ParentId = parentId, + + IsKids = isKids, + IsMovie = isMovie, + IsNews = isNews, + IsSeries = isSeries, + IsSports = isSports + }); + + return new SearchHintResult(result.Items.Select(GetSearchHintResult).ToArray(), result.TotalRecordCount); + } + + /// <summary> + /// Gets the search hint result. + /// </summary> + /// <param name="hintInfo">The hint info.</param> + /// <returns>SearchHintResult.</returns> + private SearchHint GetSearchHintResult(SearchHintInfo hintInfo) + { + var item = hintInfo.Item; + + var result = new SearchHint + { + Name = item.Name, + IndexNumber = item.IndexNumber, + ParentIndexNumber = item.ParentIndexNumber, + Id = item.Id, + Type = item.GetBaseItemKind(), + MediaType = item.MediaType, + MatchedTerm = hintInfo.MatchedTerm, + RunTimeTicks = item.RunTimeTicks, + ProductionYear = item.ProductionYear, + ChannelId = item.ChannelId, + EndDate = item.EndDate + }; + +#pragma warning disable CS0618 + // Kept for compatibility with older clients + result.ItemId = result.Id; +#pragma warning restore CS0618 + + if (item.IsFolder) + { + result.IsFolder = true; + } + + var primaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary); + + if (primaryImageTag is not null) + { + result.PrimaryImageTag = primaryImageTag; + result.PrimaryImageAspectRatio = _dtoService.GetPrimaryImageAspectRatio(item); + } + + SetThumbImageInfo(result, item); + SetBackdropImageInfo(result, item); + + switch (item) + { + case IHasSeries hasSeries: + result.Series = hasSeries.SeriesName; + break; + case LiveTvProgram program: + result.StartDate = program.StartDate; + break; + case Series series: + if (series.Status.HasValue) + { + result.Status = series.Status.Value.ToString(); + } + + break; + case MusicAlbum album: + result.Artists = album.Artists; + result.AlbumArtist = album.AlbumArtist; + break; + case Audio song: + result.AlbumArtist = song.AlbumArtists?.FirstOrDefault(); + result.Artists = song.Artists; + + MusicAlbum musicAlbum = song.AlbumEntity; + + if (musicAlbum is not null) + { + result.Album = musicAlbum.Name; + result.AlbumId = musicAlbum.Id; + } + else + { + result.Album = song.Album; + } + + break; + } + + if (!item.ChannelId.IsEmpty()) + { + var channel = _libraryManager.GetItemById<BaseItem>(item.ChannelId); + result.ChannelName = channel?.Name; + } + + return result; + } + + private void SetThumbImageInfo(SearchHint hint, BaseItem item) + { + var itemWithImage = item.HasImage(ImageType.Thumb) ? item : null; + + if (itemWithImage is null && item is Episode) + { + itemWithImage = GetParentWithImage<Series>(item, ImageType.Thumb); + } + + itemWithImage ??= GetParentWithImage<BaseItem>(item, ImageType.Thumb); + + if (itemWithImage is not null) + { + var tag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Thumb); + + if (tag is not null) + { + hint.ThumbImageTag = tag; + hint.ThumbImageItemId = itemWithImage.Id.ToString("N", CultureInfo.InvariantCulture); + } + } + } + + private void SetBackdropImageInfo(SearchHint hint, BaseItem item) + { + var itemWithImage = (item.HasImage(ImageType.Backdrop) ? item : null) + ?? GetParentWithImage<BaseItem>(item, ImageType.Backdrop); + + if (itemWithImage is not null) + { + var tag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Backdrop); + + if (tag is not null) + { + hint.BackdropImageTag = tag; + hint.BackdropImageItemId = itemWithImage.Id.ToString("N", CultureInfo.InvariantCulture); + } + } + } + + private T? GetParentWithImage<T>(BaseItem item, ImageType type) + where T : BaseItem + { + return item.GetParents().OfType<T>().FirstOrDefault(i => i.HasImage(type)); + } +} diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs new file mode 100644 index 00000000..9886d03d --- /dev/null +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -0,0 +1,453 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; +using MediaBrowser.Common.Api; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Session; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The session controller. +/// </summary> +[Route("")] +public class SessionController : BaseJellyfinApiController +{ + private readonly ISessionManager _sessionManager; + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="SessionController"/> class. + /// </summary> + /// <param name="sessionManager">Instance of <see cref="ISessionManager"/> interface.</param> + /// <param name="userManager">Instance of <see cref="IUserManager"/> interface.</param> + public SessionController( + ISessionManager sessionManager, + IUserManager userManager) + { + _sessionManager = sessionManager; + _userManager = userManager; + } + + /// <summary> + /// Gets a list of sessions. + /// </summary> + /// <param name="controllableByUserId">Filter by sessions that a given user is allowed to remote control.</param> + /// <param name="deviceId">Filter by device Id.</param> + /// <param name="activeWithinSeconds">Optional. Filter by sessions that were active in the last n seconds.</param> + /// <response code="200">List of sessions returned.</response> + /// <returns>An <see cref="IReadOnlyList{SessionInfoDto}"/> with the available sessions.</returns> + [HttpGet("Sessions")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<IReadOnlyList<SessionInfoDto>> GetSessions( + [FromQuery] Guid? controllableByUserId, + [FromQuery] string? deviceId, + [FromQuery] int? activeWithinSeconds) + { + Guid? controllableUserToCheck = controllableByUserId is null ? null : RequestHelpers.GetUserId(User, controllableByUserId); + var result = _sessionManager.GetSessions( + User.GetUserId(), + deviceId, + activeWithinSeconds, + controllableUserToCheck, + User.GetIsApiKey()); + + return Ok(result); + } + + /// <summary> + /// Instructs a session to browse to an item or view. + /// </summary> + /// <param name="sessionId">The session Id.</param> + /// <param name="itemType">The type of item to browse to.</param> + /// <param name="itemId">The Id of the item.</param> + /// <param name="itemName">The name of the item.</param> + /// <response code="204">Instruction sent to session.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Sessions/{sessionId}/Viewing")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> DisplayContent( + [FromRoute, Required] string sessionId, + [FromQuery, Required] BaseItemKind itemType, + [FromQuery, Required] string itemId, + [FromQuery, Required] string itemName) + { + var command = new BrowseRequest + { + ItemId = itemId, + ItemName = itemName, + ItemType = itemType + }; + + await _sessionManager.SendBrowseCommand( + await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false), + sessionId, + command, + CancellationToken.None) + .ConfigureAwait(false); + + return NoContent(); + } + + /// <summary> + /// Instructs a session to play an item. + /// </summary> + /// <param name="sessionId">The session id.</param> + /// <param name="playCommand">The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.</param> + /// <param name="itemIds">The ids of the items to play, comma delimited.</param> + /// <param name="startPositionTicks">The starting position of the first item.</param> + /// <param name="mediaSourceId">Optional. The media source id.</param> + /// <param name="audioStreamIndex">Optional. The index of the audio stream to play.</param> + /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to play.</param> + /// <param name="startIndex">Optional. The start index.</param> + /// <response code="204">Instruction sent to session.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Sessions/{sessionId}/Playing")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> Play( + [FromRoute, Required] string sessionId, + [FromQuery, Required] PlayCommand playCommand, + [FromQuery, Required, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] itemIds, + [FromQuery] long? startPositionTicks, + [FromQuery] string? mediaSourceId, + [FromQuery] int? audioStreamIndex, + [FromQuery] int? subtitleStreamIndex, + [FromQuery] int? startIndex) + { + var playRequest = new PlayRequest + { + ItemIds = itemIds, + StartPositionTicks = startPositionTicks, + PlayCommand = playCommand, + MediaSourceId = mediaSourceId, + AudioStreamIndex = audioStreamIndex, + SubtitleStreamIndex = subtitleStreamIndex, + StartIndex = startIndex + }; + + await _sessionManager.SendPlayCommand( + await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false), + sessionId, + playRequest, + CancellationToken.None) + .ConfigureAwait(false); + + return NoContent(); + } + + /// <summary> + /// Issues a playstate command to a client. + /// </summary> + /// <param name="sessionId">The session id.</param> + /// <param name="command">The <see cref="PlaystateCommand"/>.</param> + /// <param name="seekPositionTicks">The optional position ticks.</param> + /// <param name="controllingUserId">The optional controlling user id.</param> + /// <response code="204">Playstate command sent to session.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Sessions/{sessionId}/Playing/{command}")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> SendPlaystateCommand( + [FromRoute, Required] string sessionId, + [FromRoute, Required] PlaystateCommand command, + [FromQuery] long? seekPositionTicks, + [FromQuery] string? controllingUserId) + { + await _sessionManager.SendPlaystateCommand( + await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false), + sessionId, + new PlaystateRequest() + { + Command = command, + ControllingUserId = controllingUserId, + SeekPositionTicks = seekPositionTicks, + }, + CancellationToken.None) + .ConfigureAwait(false); + + return NoContent(); + } + + /// <summary> + /// Issues a system command to a client. + /// </summary> + /// <param name="sessionId">The session id.</param> + /// <param name="command">The command to send.</param> + /// <response code="204">System command sent to session.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Sessions/{sessionId}/System/{command}")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> SendSystemCommand( + [FromRoute, Required] string sessionId, + [FromRoute, Required] GeneralCommandType command) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var generalCommand = new GeneralCommand + { + Name = command, + ControllingUserId = currentSession.UserId + }; + + await _sessionManager.SendGeneralCommand(currentSession.Id, sessionId, generalCommand, CancellationToken.None).ConfigureAwait(false); + + return NoContent(); + } + + /// <summary> + /// Issues a general command to a client. + /// </summary> + /// <param name="sessionId">The session id.</param> + /// <param name="command">The command to send.</param> + /// <response code="204">General command sent to session.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Sessions/{sessionId}/Command/{command}")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> SendGeneralCommand( + [FromRoute, Required] string sessionId, + [FromRoute, Required] GeneralCommandType command) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + + var generalCommand = new GeneralCommand + { + Name = command, + ControllingUserId = currentSession.UserId + }; + + await _sessionManager.SendGeneralCommand(currentSession.Id, sessionId, generalCommand, CancellationToken.None) + .ConfigureAwait(false); + + return NoContent(); + } + + /// <summary> + /// Issues a full general command to a client. + /// </summary> + /// <param name="sessionId">The session id.</param> + /// <param name="command">The <see cref="GeneralCommand"/>.</param> + /// <response code="204">Full general command sent to session.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Sessions/{sessionId}/Command")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> SendFullGeneralCommand( + [FromRoute, Required] string sessionId, + [FromBody, Required] GeneralCommand command) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + + ArgumentNullException.ThrowIfNull(command); + + command.ControllingUserId = currentSession.UserId; + + await _sessionManager.SendGeneralCommand( + currentSession.Id, + sessionId, + command, + CancellationToken.None) + .ConfigureAwait(false); + + return NoContent(); + } + + /// <summary> + /// Issues a command to a client to display a message to the user. + /// </summary> + /// <param name="sessionId">The session id.</param> + /// <param name="command">The <see cref="MessageCommand" /> object containing Header, Message Text, and TimeoutMs.</param> + /// <response code="204">Message sent.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Sessions/{sessionId}/Message")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> SendMessageCommand( + [FromRoute, Required] string sessionId, + [FromBody, Required] MessageCommand command) + { + if (string.IsNullOrWhiteSpace(command.Header)) + { + command.Header = "Message from Server"; + } + + await _sessionManager.SendMessageCommand( + await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false), + sessionId, + command, + CancellationToken.None) + .ConfigureAwait(false); + + return NoContent(); + } + + /// <summary> + /// Adds an additional user to a session. + /// </summary> + /// <param name="sessionId">The session id.</param> + /// <param name="userId">The user id.</param> + /// <response code="204">User added to session.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Sessions/{sessionId}/User/{userId}")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult AddUserToSession( + [FromRoute, Required] string sessionId, + [FromRoute, Required] Guid userId) + { + _sessionManager.AddAdditionalUser(sessionId, userId); + return NoContent(); + } + + /// <summary> + /// Removes an additional user from a session. + /// </summary> + /// <param name="sessionId">The session id.</param> + /// <param name="userId">The user id.</param> + /// <response code="204">User removed from session.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpDelete("Sessions/{sessionId}/User/{userId}")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult RemoveUserFromSession( + [FromRoute, Required] string sessionId, + [FromRoute, Required] Guid userId) + { + _sessionManager.RemoveAdditionalUser(sessionId, userId); + return NoContent(); + } + + /// <summary> + /// Updates capabilities for a device. + /// </summary> + /// <param name="id">The session id.</param> + /// <param name="playableMediaTypes">A list of playable media types, comma delimited. Audio, Video, Book, Photo.</param> + /// <param name="supportedCommands">A list of supported remote control commands, comma delimited.</param> + /// <param name="supportsMediaControl">Determines whether media can be played remotely..</param> + /// <param name="supportsPersistentIdentifier">Determines whether the device supports a unique identifier.</param> + /// <response code="204">Capabilities posted.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Sessions/Capabilities")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> PostCapabilities( + [FromQuery] string? id, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] playableMediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] GeneralCommandType[] supportedCommands, + [FromQuery] bool supportsMediaControl = false, + [FromQuery] bool supportsPersistentIdentifier = true) + { + if (string.IsNullOrWhiteSpace(id)) + { + id = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + } + + _sessionManager.ReportCapabilities(id, new ClientCapabilities + { + PlayableMediaTypes = playableMediaTypes, + SupportedCommands = supportedCommands, + SupportsMediaControl = supportsMediaControl, + SupportsPersistentIdentifier = supportsPersistentIdentifier + }); + return NoContent(); + } + + /// <summary> + /// Updates capabilities for a device. + /// </summary> + /// <param name="id">The session id.</param> + /// <param name="capabilities">The <see cref="ClientCapabilities"/>.</param> + /// <response code="204">Capabilities updated.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Sessions/Capabilities/Full")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> PostFullCapabilities( + [FromQuery] string? id, + [FromBody, Required] ClientCapabilitiesDto capabilities) + { + if (string.IsNullOrWhiteSpace(id)) + { + id = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + } + + _sessionManager.ReportCapabilities(id, capabilities.ToClientCapabilities()); + + return NoContent(); + } + + /// <summary> + /// Reports that a session is viewing an item. + /// </summary> + /// <param name="sessionId">The session id.</param> + /// <param name="itemId">The item id.</param> + /// <response code="204">Session reported to server.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Sessions/Viewing")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> ReportViewing( + [FromQuery] string? sessionId, + [FromQuery, Required] string? itemId) + { + string session = sessionId ?? await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + + _sessionManager.ReportNowViewingItem(session, itemId); + return NoContent(); + } + + /// <summary> + /// Reports that a session has ended. + /// </summary> + /// <response code="204">Session end reported to server.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Sessions/Logout")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> ReportSessionEnded() + { + await _sessionManager.Logout(User.GetToken()).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Get all auth providers. + /// </summary> + /// <response code="200">Auth providers retrieved.</response> + /// <returns>An <see cref="IEnumerable{NameIdPair}"/> with the auth providers.</returns> + [HttpGet("Auth/Providers")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<IEnumerable<NameIdPair>> GetAuthProviders() + { + return _userManager.GetAuthenticationProviders(); + } + + /// <summary> + /// Get all password reset providers. + /// </summary> + /// <response code="200">Password reset providers retrieved.</response> + /// <returns>An <see cref="IEnumerable{NameIdPair}"/> with the password reset providers.</returns> + [HttpGet("Auth/PasswordResetProviders")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Authorize(Policy = Policies.RequiresElevation)] + public ActionResult<IEnumerable<NameIdPair>> GetPasswordResetProviders() + { + return _userManager.GetPasswordResetProviders(); + } +} diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs new file mode 100644 index 00000000..09f20558 --- /dev/null +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -0,0 +1,154 @@ +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using Jellyfin.Api.Models.StartupDtos; +using MediaBrowser.Common.Api; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The startup wizard controller. +/// </summary> +[Authorize(Policy = Policies.FirstTimeSetupOrElevated)] +public class StartupController : BaseJellyfinApiController +{ + private readonly IServerConfigurationManager _config; + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="StartupController" /> class. + /// </summary> + /// <param name="config">The server configuration manager.</param> + /// <param name="userManager">The user manager.</param> + public StartupController(IServerConfigurationManager config, IUserManager userManager) + { + _config = config; + _userManager = userManager; + } + + /// <summary> + /// Completes the startup wizard. + /// </summary> + /// <response code="204">Startup wizard completed.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("Complete")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult CompleteWizard() + { + _config.Configuration.IsStartupWizardCompleted = true; + _config.SaveConfiguration(); + return NoContent(); + } + + /// <summary> + /// Gets the initial startup wizard configuration. + /// </summary> + /// <response code="200">Initial startup wizard configuration retrieved.</response> + /// <returns>An <see cref="OkResult"/> containing the initial startup wizard configuration.</returns> + [HttpGet("Configuration")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<StartupConfigurationDto> GetStartupConfiguration() + { + return new StartupConfigurationDto + { + ServerName = _config.Configuration.ServerName, + UICulture = _config.Configuration.UICulture, + MetadataCountryCode = _config.Configuration.MetadataCountryCode, + PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage + }; + } + + /// <summary> + /// Sets the initial startup wizard configuration. + /// </summary> + /// <param name="startupConfiguration">The updated startup configuration.</param> + /// <response code="204">Configuration saved.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("Configuration")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult UpdateInitialConfiguration([FromBody, Required] StartupConfigurationDto startupConfiguration) + { + _config.Configuration.ServerName = startupConfiguration.ServerName ?? string.Empty; + _config.Configuration.UICulture = startupConfiguration.UICulture ?? string.Empty; + _config.Configuration.MetadataCountryCode = startupConfiguration.MetadataCountryCode ?? string.Empty; + _config.Configuration.PreferredMetadataLanguage = startupConfiguration.PreferredMetadataLanguage ?? string.Empty; + _config.SaveConfiguration(); + return NoContent(); + } + + /// <summary> + /// Sets remote access and UPnP. + /// </summary> + /// <param name="startupRemoteAccessDto">The startup remote access dto.</param> + /// <response code="204">Configuration saved.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("RemoteAccess")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult SetRemoteAccess([FromBody, Required] StartupRemoteAccessDto startupRemoteAccessDto) + { + NetworkConfiguration settings = _config.GetNetworkConfiguration(); + settings.EnableRemoteAccess = startupRemoteAccessDto.EnableRemoteAccess; + _config.SaveConfiguration(NetworkConfigurationStore.StoreKey, settings); + return NoContent(); + } + + /// <summary> + /// Gets the first user. + /// </summary> + /// <response code="200">Initial user retrieved.</response> + /// <returns>The first user.</returns> + [HttpGet("User")] + [HttpGet("FirstUser", Name = "GetFirstUser_2")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<StartupUserDto> GetFirstUser() + { + // TODO: Remove this method when startup wizard no longer requires an existing user. + await _userManager.InitializeAsync().ConfigureAwait(false); + var user = _userManager.Users.First(); + return new StartupUserDto + { + Name = user.Username + }; + } + + /// <summary> + /// Sets the user name and password. + /// </summary> + /// <param name="startupUserDto">The DTO containing username and password.</param> + /// <response code="204">Updated user name and password.</response> + /// <returns> + /// A <see cref="Task" /> that represents the asynchronous update operation. + /// The task result contains a <see cref="NoContentResult"/> indicating success. + /// </returns> + [HttpPost("User")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> UpdateStartupUser([FromBody] StartupUserDto startupUserDto) + { + var user = _userManager.Users.First(); + if (string.IsNullOrWhiteSpace(startupUserDto.Password)) + { + return BadRequest("Password must not be empty"); + } + + if (startupUserDto.Name is not null) + { + user.Username = startupUserDto.Name; + } + + await _userManager.UpdateUserAsync(user).ConfigureAwait(false); + + if (!string.IsNullOrEmpty(startupUserDto.Password)) + { + await _userManager.ChangePassword(user, startupUserDto.Password).ConfigureAwait(false); + } + + return NoContent(); + } +} diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs new file mode 100644 index 00000000..ad08dc5f --- /dev/null +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -0,0 +1,156 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Studios controller. +/// </summary> +[Authorize] +public class StudiosController : BaseJellyfinApiController +{ + private readonly ILibraryManager _libraryManager; + private readonly IUserManager _userManager; + private readonly IDtoService _dtoService; + + /// <summary> + /// Initializes a new instance of the <see cref="StudiosController"/> class. + /// </summary> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> + public StudiosController( + ILibraryManager libraryManager, + IUserManager userManager, + IDtoService dtoService) + { + _libraryManager = libraryManager; + _userManager = userManager; + _dtoService = dtoService; + } + + /// <summary> + /// Gets all studios from a given item, folder, or the entire library. + /// </summary> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="searchTerm">Optional. Search term.</param> + /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param> + /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param> + /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param> + /// <param name="enableUserData">Optional, include user data.</param> + /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="userId">User id.</param> + /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param> + /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param> + /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param> + /// <param name="enableImages">Optional, include image information in output.</param> + /// <param name="enableTotalRecordCount">Total record count.</param> + /// <response code="200">Studios returned.</response> + /// <returns>An <see cref="OkResult"/> containing the studios.</returns> + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetStudios( + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] string? searchTerm, + [FromQuery] Guid? parentId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery] bool? isFavorite, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery] Guid? userId, + [FromQuery] string? nameStartsWithOrGreater, + [FromQuery] string? nameStartsWith, + [FromQuery] string? nameLessThan, + [FromQuery] bool? enableImages = true, + [FromQuery] bool enableTotalRecordCount = true) + { + userId = RequestHelpers.GetUserId(User, userId); + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + + User? user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + + var parentItem = _libraryManager.GetParentItem(parentId, userId); + + var query = new InternalItemsQuery(user) + { + ExcludeItemTypes = excludeItemTypes, + IncludeItemTypes = includeItemTypes, + StartIndex = startIndex, + Limit = limit, + IsFavorite = isFavorite, + NameLessThan = nameLessThan, + NameStartsWith = nameStartsWith, + NameStartsWithOrGreater = nameStartsWithOrGreater, + DtoOptions = dtoOptions, + SearchTerm = searchTerm, + EnableTotalRecordCount = enableTotalRecordCount + }; + + if (parentId.HasValue) + { + if (parentItem is Folder) + { + query.AncestorIds = new[] { parentId.Value }; + } + else + { + query.ItemIds = new[] { parentId.Value }; + } + } + + var result = _libraryManager.GetStudios(query); + var shouldIncludeItemTypes = includeItemTypes.Length != 0; + return RequestHelpers.CreateQueryResult(result, dtoOptions, _dtoService, shouldIncludeItemTypes, user); + } + + /// <summary> + /// Gets a studio by name. + /// </summary> + /// <param name="name">Studio name.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <response code="200">Studio returned.</response> + /// <returns>An <see cref="OkResult"/> containing the studio.</returns> + [HttpGet("{name}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<BaseItemDto> GetStudio([FromRoute, Required] string name, [FromQuery] Guid? userId) + { + userId = RequestHelpers.GetUserId(User, userId); + var dtoOptions = new DtoOptions(); + + var item = _libraryManager.GetStudio(name); + if (!userId.IsNullOrEmpty()) + { + var user = _userManager.GetUserById(userId.Value); + + return _dtoService.GetBaseItemDto(item, dtoOptions, user); + } + + return _dtoService.GetBaseItemDto(item, dtoOptions); + } +} diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs new file mode 100644 index 00000000..e5df873f --- /dev/null +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -0,0 +1,580 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net.Mime; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Attributes; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.Models.SubtitleDtos; +using MediaBrowser.Common.Api; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Providers; +using MediaBrowser.Model.Subtitles; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Subtitle controller. +/// </summary> +[Route("")] +public class SubtitleController : BaseJellyfinApiController +{ + private readonly IServerConfigurationManager _serverConfigurationManager; + private readonly ILibraryManager _libraryManager; + private readonly ISubtitleManager _subtitleManager; + private readonly ISubtitleEncoder _subtitleEncoder; + private readonly IMediaSourceManager _mediaSourceManager; + private readonly IProviderManager _providerManager; + private readonly IFileSystem _fileSystem; + private readonly ILogger<SubtitleController> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="SubtitleController"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="libraryManager">Instance of <see cref="ILibraryManager"/> interface.</param> + /// <param name="subtitleManager">Instance of <see cref="ISubtitleManager"/> interface.</param> + /// <param name="subtitleEncoder">Instance of <see cref="ISubtitleEncoder"/> interface.</param> + /// <param name="mediaSourceManager">Instance of <see cref="IMediaSourceManager"/> interface.</param> + /// <param name="providerManager">Instance of <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of <see cref="IFileSystem"/> interface.</param> + /// <param name="logger">Instance of <see cref="ILogger{SubtitleController}"/> interface.</param> + public SubtitleController( + IServerConfigurationManager serverConfigurationManager, + ILibraryManager libraryManager, + ISubtitleManager subtitleManager, + ISubtitleEncoder subtitleEncoder, + IMediaSourceManager mediaSourceManager, + IProviderManager providerManager, + IFileSystem fileSystem, + ILogger<SubtitleController> logger) + { + _serverConfigurationManager = serverConfigurationManager; + _libraryManager = libraryManager; + _subtitleManager = subtitleManager; + _subtitleEncoder = subtitleEncoder; + _mediaSourceManager = mediaSourceManager; + _providerManager = providerManager; + _fileSystem = fileSystem; + _logger = logger; + } + + /// <summary> + /// Deletes an external subtitle file. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="index">The index of the subtitle file.</param> + /// <response code="204">Subtitle deleted.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpDelete("Videos/{itemId}/Subtitles/{index}")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> DeleteSubtitle( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] int index) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + await _subtitleManager.DeleteSubtitles(item, index).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Search remote subtitles. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="language">The language of the subtitles.</param> + /// <param name="isPerfectMatch">Optional. Only show subtitles which are a perfect match.</param> + /// <response code="200">Subtitles retrieved.</response> + /// <response code="404">Item not found.</response> + /// <returns>An array of <see cref="RemoteSubtitleInfo"/>.</returns> + [HttpGet("Items/{itemId}/RemoteSearch/Subtitles/{language}")] + [Authorize(Policy = Policies.SubtitleManagement)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] string language, + [FromQuery] bool? isPerfectMatch) + { + var item = _libraryManager.GetItemById<Video>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + return await _subtitleManager.SearchSubtitles(item, language, isPerfectMatch, false, CancellationToken.None).ConfigureAwait(false); + } + + /// <summary> + /// Downloads a remote subtitle. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="subtitleId">The subtitle id.</param> + /// <response code="204">Subtitle downloaded.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Items/{itemId}/RemoteSearch/Subtitles/{subtitleId}")] + [Authorize(Policy = Policies.SubtitleManagement)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> DownloadRemoteSubtitles( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] string subtitleId) + { + var item = _libraryManager.GetItemById<Video>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + try + { + await _subtitleManager.DownloadSubtitles(item, subtitleId, CancellationToken.None) + .ConfigureAwait(false); + + _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error downloading subtitles"); + } + + return NoContent(); + } + + /// <summary> + /// Gets the remote subtitles. + /// </summary> + /// <param name="subtitleId">The item id.</param> + /// <response code="200">File returned.</response> + /// <returns>A <see cref="FileStreamResult"/> with the subtitle file.</returns> + [HttpGet("Providers/Subtitles/Subtitles/{subtitleId}")] + [Authorize(Policy = Policies.SubtitleManagement)] + [ProducesResponseType(StatusCodes.Status200OK)] + [Produces(MediaTypeNames.Application.Octet)] + [ProducesFile("text/*")] + public async Task<ActionResult> GetRemoteSubtitles([FromRoute, Required] string subtitleId) + { + var result = await _subtitleManager.GetRemoteSubtitles(subtitleId, CancellationToken.None).ConfigureAwait(false); + + return File(result.Stream, MimeTypes.GetMimeType("file." + result.Format)); + } + + /// <summary> + /// Gets subtitles in a specified format. + /// </summary> + /// <param name="routeItemId">The (route) item id.</param> + /// <param name="routeMediaSourceId">The (route) media source id.</param> + /// <param name="routeIndex">The (route) subtitle stream index.</param> + /// <param name="routeFormat">The (route) format of the returned subtitle.</param> + /// <param name="itemId">The item id.</param> + /// <param name="mediaSourceId">The media source id.</param> + /// <param name="index">The subtitle stream index.</param> + /// <param name="format">The format of the returned subtitle.</param> + /// <param name="endPositionTicks">Optional. The end position of the subtitle in ticks.</param> + /// <param name="copyTimestamps">Optional. Whether to copy the timestamps.</param> + /// <param name="addVttTimeMap">Optional. Whether to add a VTT time map.</param> + /// <param name="startPositionTicks">The start position of the subtitle in ticks.</param> + /// <response code="200">File returned.</response> + /// <returns>A <see cref="FileContentResult"/> with the subtitle file.</returns> + [HttpGet("Videos/{routeItemId}/{routeMediaSourceId}/Subtitles/{routeIndex}/Stream.{routeFormat}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesFile("text/*")] + public async Task<ActionResult> GetSubtitle( + [FromRoute, Required] Guid routeItemId, + [FromRoute, Required] string routeMediaSourceId, + [FromRoute, Required] int routeIndex, + [FromRoute, Required] string routeFormat, + [FromQuery, ParameterObsolete] Guid? itemId, + [FromQuery, ParameterObsolete] string? mediaSourceId, + [FromQuery, ParameterObsolete] int? index, + [FromQuery, ParameterObsolete] string? format, + [FromQuery] long? endPositionTicks, + [FromQuery] bool copyTimestamps = false, + [FromQuery] bool addVttTimeMap = false, + [FromQuery] long startPositionTicks = 0) + { + // Set parameters to route value if not provided via query. + itemId ??= routeItemId; + mediaSourceId ??= routeMediaSourceId; + index ??= routeIndex; + format ??= routeFormat; + + if (string.Equals(format, "js", StringComparison.OrdinalIgnoreCase)) + { + format = "json"; + } + + if (string.IsNullOrEmpty(format)) + { + var item = _libraryManager.GetItemById<Video>(itemId.Value); + + var idString = itemId.Value.ToString("N", CultureInfo.InvariantCulture); + var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false) + .First(i => string.Equals(i.Id, mediaSourceId ?? idString, StringComparison.Ordinal)); + + var subtitleStream = mediaSource.MediaStreams + .First(i => i.Type == MediaStreamType.Subtitle && i.Index == index); + + return PhysicalFile(subtitleStream.Path, MimeTypes.GetMimeType(subtitleStream.Path)); + } + + if (string.Equals(format, "vtt", StringComparison.OrdinalIgnoreCase) && addVttTimeMap) + { + Stream stream = await EncodeSubtitles(itemId.Value, mediaSourceId, index.Value, format, startPositionTicks, endPositionTicks, copyTimestamps).ConfigureAwait(false); + await using (stream.ConfigureAwait(false)) + { + using var reader = new StreamReader(stream); + + var text = await reader.ReadToEndAsync().ConfigureAwait(false); + + text = text.Replace("WEBVTT", "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000", StringComparison.Ordinal); + + return File(Encoding.UTF8.GetBytes(text), MimeTypes.GetMimeType("file." + format)); + } + } + + return File( + await EncodeSubtitles( + itemId.Value, + mediaSourceId, + index.Value, + format, + startPositionTicks, + endPositionTicks, + copyTimestamps).ConfigureAwait(false), + MimeTypes.GetMimeType("file." + format)); + } + + /// <summary> + /// Gets subtitles in a specified format. + /// </summary> + /// <param name="routeItemId">The (route) item id.</param> + /// <param name="routeMediaSourceId">The (route) media source id.</param> + /// <param name="routeIndex">The (route) subtitle stream index.</param> + /// <param name="routeStartPositionTicks">The (route) start position of the subtitle in ticks.</param> + /// <param name="routeFormat">The (route) format of the returned subtitle.</param> + /// <param name="itemId">The item id.</param> + /// <param name="mediaSourceId">The media source id.</param> + /// <param name="index">The subtitle stream index.</param> + /// <param name="startPositionTicks">The start position of the subtitle in ticks.</param> + /// <param name="format">The format of the returned subtitle.</param> + /// <param name="endPositionTicks">Optional. The end position of the subtitle in ticks.</param> + /// <param name="copyTimestamps">Optional. Whether to copy the timestamps.</param> + /// <param name="addVttTimeMap">Optional. Whether to add a VTT time map.</param> + /// <response code="200">File returned.</response> + /// <returns>A <see cref="FileContentResult"/> with the subtitle file.</returns> + [HttpGet("Videos/{routeItemId}/{routeMediaSourceId}/Subtitles/{routeIndex}/{routeStartPositionTicks}/Stream.{routeFormat}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesFile("text/*")] + public Task<ActionResult> GetSubtitleWithTicks( + [FromRoute, Required] Guid routeItemId, + [FromRoute, Required] string routeMediaSourceId, + [FromRoute, Required] int routeIndex, + [FromRoute, Required] long routeStartPositionTicks, + [FromRoute, Required] string routeFormat, + [FromQuery, ParameterObsolete] Guid? itemId, + [FromQuery, ParameterObsolete] string? mediaSourceId, + [FromQuery, ParameterObsolete] int? index, + [FromQuery, ParameterObsolete] long? startPositionTicks, + [FromQuery, ParameterObsolete] string? format, + [FromQuery] long? endPositionTicks, + [FromQuery] bool copyTimestamps = false, + [FromQuery] bool addVttTimeMap = false) + { + return GetSubtitle( + routeItemId, + routeMediaSourceId, + routeIndex, + routeFormat, + itemId, + mediaSourceId, + index, + format, + endPositionTicks, + copyTimestamps, + addVttTimeMap, + startPositionTicks ?? routeStartPositionTicks); + } + + /// <summary> + /// Gets an HLS subtitle playlist. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="index">The subtitle stream index.</param> + /// <param name="mediaSourceId">The media source id.</param> + /// <param name="segmentLength">The subtitle segment length.</param> + /// <response code="200">Subtitle playlist retrieved.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="FileContentResult"/> with the HLS subtitle playlist.</returns> + [HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/subtitles.m3u8")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesPlaylistFile] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] + public async Task<ActionResult> GetSubtitlePlaylist( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] int index, + [FromRoute, Required] string mediaSourceId, + [FromQuery, Required] int segmentLength) + { + var item = _libraryManager.GetItemById<Video>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + var mediaSource = await _mediaSourceManager.GetMediaSource(item, mediaSourceId, null, false, CancellationToken.None).ConfigureAwait(false); + + var runtime = mediaSource.RunTimeTicks ?? -1; + + if (runtime <= 0) + { + throw new ArgumentException("HLS Subtitles are not supported for this media."); + } + + var segmentLengthTicks = TimeSpan.FromSeconds(segmentLength).Ticks; + if (segmentLengthTicks <= 0) + { + throw new ArgumentException("segmentLength was not given, or it was given incorrectly. (It should be bigger than 0)"); + } + + var builder = new StringBuilder(); + builder.AppendLine("#EXTM3U") + .Append("#EXT-X-TARGETDURATION:") + .Append(segmentLength) + .AppendLine() + .AppendLine("#EXT-X-VERSION:3") + .AppendLine("#EXT-X-MEDIA-SEQUENCE:0") + .AppendLine("#EXT-X-PLAYLIST-TYPE:VOD"); + + long positionTicks = 0; + + var accessToken = User.GetToken(); + + while (positionTicks < runtime) + { + var remaining = runtime - positionTicks; + var lengthTicks = Math.Min(remaining, segmentLengthTicks); + + builder.Append("#EXTINF:") + .Append(TimeSpan.FromTicks(lengthTicks).TotalSeconds) + .Append(',') + .AppendLine(); + + var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks); + + var url = string.Format( + CultureInfo.InvariantCulture, + "stream.vtt?CopyTimestamps=true&AddVttTimeMap=true&StartPositionTicks={0}&EndPositionTicks={1}&ApiKey={2}", + positionTicks.ToString(CultureInfo.InvariantCulture), + endPositionTicks.ToString(CultureInfo.InvariantCulture), + accessToken); + + builder.AppendLine(url); + + positionTicks += segmentLengthTicks; + } + + builder.AppendLine("#EXT-X-ENDLIST"); + return File(Encoding.UTF8.GetBytes(builder.ToString()), MimeTypes.GetMimeType("playlist.m3u8")); + } + + /// <summary> + /// Upload an external subtitle file. + /// </summary> + /// <param name="itemId">The item the subtitle belongs to.</param> + /// <param name="body">The request body.</param> + /// <response code="204">Subtitle uploaded.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Videos/{itemId}/Subtitles")] + [Authorize(Policy = Policies.SubtitleManagement)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> UploadSubtitle( + [FromRoute, Required] Guid itemId, + [FromBody, Required] UploadSubtitleDto body) + { + var item = _libraryManager.GetItemById<Video>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + var bytes = Encoding.UTF8.GetBytes(body.Data); + var memoryStream = new MemoryStream(bytes, 0, bytes.Length, false, true); + await using (memoryStream.ConfigureAwait(false)) + { + using var transform = new FromBase64Transform(); + var stream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Read); + await using (stream.ConfigureAwait(false)) + { + await _subtitleManager.UploadSubtitle( + item, + new SubtitleResponse + { + Format = body.Format, + Language = body.Language, + IsForced = body.IsForced, + IsHearingImpaired = body.IsHearingImpaired, + Stream = stream + }).ConfigureAwait(false); + _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); + + return NoContent(); + } + } + } + + /// <summary> + /// Encodes a subtitle in the specified format. + /// </summary> + /// <param name="id">The media id.</param> + /// <param name="mediaSourceId">The source media id.</param> + /// <param name="index">The subtitle index.</param> + /// <param name="format">The format to convert to.</param> + /// <param name="startPositionTicks">The start position in ticks.</param> + /// <param name="endPositionTicks">The end position in ticks.</param> + /// <param name="copyTimestamps">Whether to copy the timestamps.</param> + /// <returns>A <see cref="Task{Stream}"/> with the new subtitle file.</returns> + private Task<Stream> EncodeSubtitles( + Guid id, + string? mediaSourceId, + int index, + string format, + long startPositionTicks, + long? endPositionTicks, + bool copyTimestamps) + { + var item = _libraryManager.GetItemById<BaseItem>(id); + + return _subtitleEncoder.GetSubtitles( + item, + mediaSourceId, + index, + format, + startPositionTicks, + endPositionTicks ?? 0, + copyTimestamps, + CancellationToken.None); + } + + /// <summary> + /// Gets a list of available fallback font files. + /// </summary> + /// <response code="200">Information retrieved.</response> + /// <returns>An array of <see cref="FontFile"/> with the available font files.</returns> + [HttpGet("FallbackFont/Fonts")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + public IEnumerable<FontFile> GetFallbackFontList() + { + var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); + var fallbackFontPath = encodingOptions.FallbackFontPath; + + if (!string.IsNullOrEmpty(fallbackFontPath)) + { + var files = _fileSystem.GetFiles(fallbackFontPath, new[] { ".woff", ".woff2", ".ttf", ".otf" }, false, false); + var fontFiles = files + .Select(i => new FontFile + { + Name = i.Name, + Size = i.Length, + DateCreated = _fileSystem.GetCreationTimeUtc(i), + DateModified = _fileSystem.GetLastWriteTimeUtc(i) + }) + .OrderBy(i => i.Size) + .ThenBy(i => i.Name) + .ThenByDescending(i => i.DateModified) + .ThenByDescending(i => i.DateCreated); + // max total size 20M + const int MaxSize = 20971520; + var sizeCounter = 0L; + foreach (var fontFile in fontFiles) + { + sizeCounter += fontFile.Size; + if (sizeCounter >= MaxSize) + { + _logger.LogWarning("Some fonts will not be sent due to size limitations"); + yield break; + } + + yield return fontFile; + } + } + else + { + _logger.LogWarning("The path of fallback font folder has not been set"); + encodingOptions.EnableFallbackFont = false; + } + } + + /// <summary> + /// Gets a fallback font file. + /// </summary> + /// <param name="name">The name of the fallback font file to get.</param> + /// <response code="200">Fallback font file retrieved.</response> + /// <returns>The fallback font file.</returns> + [HttpGet("FallbackFont/Fonts/{name}")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesFile("font/*")] + public ActionResult GetFallbackFont([FromRoute, Required] string name) + { + var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); + var fallbackFontPath = encodingOptions.FallbackFontPath; + + if (!string.IsNullOrEmpty(fallbackFontPath)) + { + var fontFile = _fileSystem.GetFiles(fallbackFontPath) + .First(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase)); + var fileSize = fontFile?.Length; + + if (fontFile is not null && fileSize is not null && fileSize > 0) + { + _logger.LogDebug("Fallback font size is {FileSize} Bytes", fileSize); + return PhysicalFile(fontFile.FullName, MimeTypes.GetMimeType(fontFile.FullName)); + } + + _logger.LogWarning("The selected font is null or empty"); + } + else + { + _logger.LogWarning("The path of fallback font folder has not been set"); + encodingOptions.EnableFallbackFont = false; + } + + // returning HTTP 204 will break the SubtitlesOctopus + return Ok(); + } +} diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs new file mode 100644 index 00000000..e9e40407 --- /dev/null +++ b/Jellyfin.Api/Controllers/SuggestionsController.cs @@ -0,0 +1,125 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The suggestions controller. +/// </summary> +[Route("")] +[Authorize] +public class SuggestionsController : BaseJellyfinApiController +{ + private readonly IDtoService _dtoService; + private readonly IUserManager _userManager; + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// Initializes a new instance of the <see cref="SuggestionsController"/> class. + /// </summary> + /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + public SuggestionsController( + IDtoService dtoService, + IUserManager userManager, + ILibraryManager libraryManager) + { + _dtoService = dtoService; + _userManager = userManager; + _libraryManager = libraryManager; + } + + /// <summary> + /// Gets suggestions. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="mediaType">The media types.</param> + /// <param name="type">The type.</param> + /// <param name="startIndex">Optional. The start index.</param> + /// <param name="limit">Optional. The limit.</param> + /// <param name="enableTotalRecordCount">Whether to enable the total record count.</param> + /// <response code="200">Suggestions returned.</response> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the suggestions.</returns> + [HttpGet("Items/Suggestions")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetSuggestions( + [FromQuery] Guid? userId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaType, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] type, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] bool enableTotalRecordCount = false) + { + User? user; + if (userId.IsNullOrEmpty()) + { + user = null; + } + else + { + var requestUserId = RequestHelpers.GetUserId(User, userId); + user = _userManager.GetUserById(requestUserId); + } + + var dtoOptions = new DtoOptions(); + var result = _libraryManager.GetItemsResult(new InternalItemsQuery(user) + { + OrderBy = new[] { (ItemSortBy.Random, SortOrder.Descending) }, + MediaTypes = mediaType, + IncludeItemTypes = type, + IsVirtualItem = false, + StartIndex = startIndex, + Limit = limit, + DtoOptions = dtoOptions, + EnableTotalRecordCount = enableTotalRecordCount, + Recursive = true + }); + + var dtoList = _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user); + + return new QueryResult<BaseItemDto>( + startIndex, + result.TotalRecordCount, + dtoList); + } + + /// <summary> + /// Gets suggestions. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="mediaType">The media types.</param> + /// <param name="type">The type.</param> + /// <param name="startIndex">Optional. The start index.</param> + /// <param name="limit">Optional. The limit.</param> + /// <param name="enableTotalRecordCount">Whether to enable the total record count.</param> + /// <response code="200">Suggestions returned.</response> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the suggestions.</returns> + [HttpGet("Users/{userId}/Suggestions")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult<QueryResult<BaseItemDto>> GetSuggestionsLegacy( + [FromRoute, Required] Guid userId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaType, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] type, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] bool enableTotalRecordCount = false) + => GetSuggestions(userId, mediaType, type, startIndex, limit, enableTotalRecordCount); +} diff --git a/Jellyfin.Api/Controllers/SyncPlayController.cs b/Jellyfin.Api/Controllers/SyncPlayController.cs new file mode 100644 index 00000000..3d687407 --- /dev/null +++ b/Jellyfin.Api/Controllers/SyncPlayController.cs @@ -0,0 +1,440 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.Models.SyncPlayDtos; +using MediaBrowser.Common.Api; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.SyncPlay; +using MediaBrowser.Controller.SyncPlay.PlaybackRequests; +using MediaBrowser.Controller.SyncPlay.Requests; +using MediaBrowser.Model.SyncPlay; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The sync play controller. +/// </summary> +[Authorize(Policy = Policies.SyncPlayHasAccess)] +public class SyncPlayController : BaseJellyfinApiController +{ + private readonly ISessionManager _sessionManager; + private readonly ISyncPlayManager _syncPlayManager; + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="SyncPlayController"/> class. + /// </summary> + /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param> + /// <param name="syncPlayManager">Instance of the <see cref="ISyncPlayManager"/> interface.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + public SyncPlayController( + ISessionManager sessionManager, + ISyncPlayManager syncPlayManager, + IUserManager userManager) + { + _sessionManager = sessionManager; + _syncPlayManager = syncPlayManager; + _userManager = userManager; + } + + /// <summary> + /// Create a new SyncPlay group. + /// </summary> + /// <param name="requestData">The settings of the new group.</param> + /// <response code="204">New group created.</response> + /// <returns>An <see cref="GroupInfoDto"/> for the created group.</returns> + [HttpPost("New")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Authorize(Policy = Policies.SyncPlayCreateGroup)] + public async Task<ActionResult<GroupInfoDto>> SyncPlayCreateGroup( + [FromBody, Required] NewGroupRequestDto requestData) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new NewGroupRequest(requestData.GroupName); + return Ok(_syncPlayManager.NewGroup(currentSession, syncPlayRequest, CancellationToken.None)); + } + + /// <summary> + /// Join an existing SyncPlay group. + /// </summary> + /// <param name="requestData">The group to join.</param> + /// <response code="204">Group join successful.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("Join")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayJoinGroup)] + public async Task<ActionResult> SyncPlayJoinGroup( + [FromBody, Required] JoinGroupRequestDto requestData) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new JoinGroupRequest(requestData.GroupId); + _syncPlayManager.JoinGroup(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Leave the joined SyncPlay group. + /// </summary> + /// <response code="204">Group leave successful.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("Leave")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayIsInGroup)] + public async Task<ActionResult> SyncPlayLeaveGroup() + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new LeaveGroupRequest(); + _syncPlayManager.LeaveGroup(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Gets all SyncPlay groups. + /// </summary> + /// <response code="200">Groups returned.</response> + /// <returns>An <see cref="IEnumerable{GroupInfoView}"/> containing the available SyncPlay groups.</returns> + [HttpGet("List")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Authorize(Policy = Policies.SyncPlayJoinGroup)] + public async Task<ActionResult<IEnumerable<GroupInfoDto>>> SyncPlayGetGroups() + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new ListGroupsRequest(); + return Ok(_syncPlayManager.ListGroups(currentSession, syncPlayRequest).AsEnumerable()); + } + + /// <summary> + /// Gets a SyncPlay group by id. + /// </summary> + /// <param name="id">The id of the group.</param> + /// <response code="200">Group returned.</response> + /// <returns>An <see cref="GroupInfoDto"/> for the requested group.</returns> + [HttpGet("{id:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Authorize(Policy = Policies.SyncPlayJoinGroup)] + public async Task<ActionResult<GroupInfoDto>> SyncPlayGetGroup([FromRoute] Guid id) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var group = _syncPlayManager.GetGroup(currentSession, id); + return group is null ? NotFound() : Ok(group); + } + + /// <summary> + /// Request to set new playlist in SyncPlay group. + /// </summary> + /// <param name="requestData">The new playlist to play in the group.</param> + /// <response code="204">Queue update sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("SetNewQueue")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayIsInGroup)] + public async Task<ActionResult> SyncPlaySetNewQueue( + [FromBody, Required] PlayRequestDto requestData) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new PlayGroupRequest( + requestData.PlayingQueue, + requestData.PlayingItemPosition, + requestData.StartPositionTicks); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request to change playlist item in SyncPlay group. + /// </summary> + /// <param name="requestData">The new item to play.</param> + /// <response code="204">Queue update sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("SetPlaylistItem")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayIsInGroup)] + public async Task<ActionResult> SyncPlaySetPlaylistItem( + [FromBody, Required] SetPlaylistItemRequestDto requestData) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new SetPlaylistItemGroupRequest(requestData.PlaylistItemId); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request to remove items from the playlist in SyncPlay group. + /// </summary> + /// <param name="requestData">The items to remove.</param> + /// <response code="204">Queue update sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("RemoveFromPlaylist")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayIsInGroup)] + public async Task<ActionResult> SyncPlayRemoveFromPlaylist( + [FromBody, Required] RemoveFromPlaylistRequestDto requestData) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new RemoveFromPlaylistGroupRequest(requestData.PlaylistItemIds, requestData.ClearPlaylist, requestData.ClearPlayingItem); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request to move an item in the playlist in SyncPlay group. + /// </summary> + /// <param name="requestData">The new position for the item.</param> + /// <response code="204">Queue update sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("MovePlaylistItem")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayIsInGroup)] + public async Task<ActionResult> SyncPlayMovePlaylistItem( + [FromBody, Required] MovePlaylistItemRequestDto requestData) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new MovePlaylistItemGroupRequest(requestData.PlaylistItemId, requestData.NewIndex); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request to queue items to the playlist of a SyncPlay group. + /// </summary> + /// <param name="requestData">The items to add.</param> + /// <response code="204">Queue update sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("Queue")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayIsInGroup)] + public async Task<ActionResult> SyncPlayQueue( + [FromBody, Required] QueueRequestDto requestData) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new QueueGroupRequest(requestData.ItemIds, requestData.Mode); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request unpause in SyncPlay group. + /// </summary> + /// <response code="204">Unpause update sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("Unpause")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayIsInGroup)] + public async Task<ActionResult> SyncPlayUnpause() + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new UnpauseGroupRequest(); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request pause in SyncPlay group. + /// </summary> + /// <response code="204">Pause update sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("Pause")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayIsInGroup)] + public async Task<ActionResult> SyncPlayPause() + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new PauseGroupRequest(); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request stop in SyncPlay group. + /// </summary> + /// <response code="204">Stop update sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("Stop")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayIsInGroup)] + public async Task<ActionResult> SyncPlayStop() + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new StopGroupRequest(); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request seek in SyncPlay group. + /// </summary> + /// <param name="requestData">The new playback position.</param> + /// <response code="204">Seek update sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("Seek")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayIsInGroup)] + public async Task<ActionResult> SyncPlaySeek( + [FromBody, Required] SeekRequestDto requestData) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new SeekGroupRequest(requestData.PositionTicks); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Notify SyncPlay group that member is buffering. + /// </summary> + /// <param name="requestData">The player status.</param> + /// <response code="204">Group state update sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("Buffering")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayIsInGroup)] + public async Task<ActionResult> SyncPlayBuffering( + [FromBody, Required] BufferRequestDto requestData) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new BufferGroupRequest( + requestData.When, + requestData.PositionTicks, + requestData.IsPlaying, + requestData.PlaylistItemId); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Notify SyncPlay group that member is ready for playback. + /// </summary> + /// <param name="requestData">The player status.</param> + /// <response code="204">Group state update sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("Ready")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayIsInGroup)] + public async Task<ActionResult> SyncPlayReady( + [FromBody, Required] ReadyRequestDto requestData) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new ReadyGroupRequest( + requestData.When, + requestData.PositionTicks, + requestData.IsPlaying, + requestData.PlaylistItemId); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request SyncPlay group to ignore member during group-wait. + /// </summary> + /// <param name="requestData">The settings to set.</param> + /// <response code="204">Member state updated.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("SetIgnoreWait")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayIsInGroup)] + public async Task<ActionResult> SyncPlaySetIgnoreWait( + [FromBody, Required] IgnoreWaitRequestDto requestData) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new IgnoreWaitGroupRequest(requestData.IgnoreWait); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request next item in SyncPlay group. + /// </summary> + /// <param name="requestData">The current item information.</param> + /// <response code="204">Next item update sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("NextItem")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayIsInGroup)] + public async Task<ActionResult> SyncPlayNextItem( + [FromBody, Required] NextItemRequestDto requestData) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new NextItemGroupRequest(requestData.PlaylistItemId); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request previous item in SyncPlay group. + /// </summary> + /// <param name="requestData">The current item information.</param> + /// <response code="204">Previous item update sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("PreviousItem")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayIsInGroup)] + public async Task<ActionResult> SyncPlayPreviousItem( + [FromBody, Required] PreviousItemRequestDto requestData) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new PreviousItemGroupRequest(requestData.PlaylistItemId); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request to set repeat mode in SyncPlay group. + /// </summary> + /// <param name="requestData">The new repeat mode.</param> + /// <response code="204">Play queue update sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("SetRepeatMode")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayIsInGroup)] + public async Task<ActionResult> SyncPlaySetRepeatMode( + [FromBody, Required] SetRepeatModeRequestDto requestData) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new SetRepeatModeGroupRequest(requestData.Mode); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request to set shuffle mode in SyncPlay group. + /// </summary> + /// <param name="requestData">The new shuffle mode.</param> + /// <response code="204">Play queue update sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("SetShuffleMode")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayIsInGroup)] + public async Task<ActionResult> SyncPlaySetShuffleMode( + [FromBody, Required] SetShuffleModeRequestDto requestData) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new SetShuffleModeGroupRequest(requestData.Mode); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Update session ping. + /// </summary> + /// <param name="requestData">The new ping.</param> + /// <response code="204">Ping updated.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("Ping")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<ActionResult> SyncPlayPing( + [FromBody, Required] PingRequestDto requestData) + { + var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); + var syncPlayRequest = new PingGroupRequest(requestData.Ping); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } +} diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs new file mode 100644 index 00000000..450225c3 --- /dev/null +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using System.Net.Mime; +using Jellyfin.Api.Attributes; +using Jellyfin.Api.Models.SystemInfoDtos; +using MediaBrowser.Common.Api; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.System; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The system controller. +/// </summary> +public class SystemController : BaseJellyfinApiController +{ + private readonly ILogger<SystemController> _logger; + private readonly IServerApplicationHost _appHost; + private readonly IApplicationPaths _appPaths; + private readonly IFileSystem _fileSystem; + private readonly INetworkManager _networkManager; + private readonly ISystemManager _systemManager; + + /// <summary> + /// Initializes a new instance of the <see cref="SystemController"/> class. + /// </summary> + /// <param name="logger">Instance of <see cref="ILogger{SystemController}"/> interface.</param> + /// <param name="appPaths">Instance of <see cref="IServerApplicationPaths"/> interface.</param> + /// <param name="appHost">Instance of <see cref="IServerApplicationHost"/> interface.</param> + /// <param name="fileSystem">Instance of <see cref="IFileSystem"/> interface.</param> + /// <param name="networkManager">Instance of <see cref="INetworkManager"/> interface.</param> + /// <param name="systemManager">Instance of <see cref="ISystemManager"/> interface.</param> + public SystemController( + ILogger<SystemController> logger, + IServerApplicationHost appHost, + IServerApplicationPaths appPaths, + IFileSystem fileSystem, + INetworkManager networkManager, + ISystemManager systemManager) + { + _logger = logger; + _appHost = appHost; + _appPaths = appPaths; + _fileSystem = fileSystem; + _networkManager = networkManager; + _systemManager = systemManager; + } + + /// <summary> + /// Gets information about the server. + /// </summary> + /// <response code="200">Information retrieved.</response> + /// <response code="403">User does not have permission to retrieve information.</response> + /// <returns>A <see cref="SystemInfo"/> with info about the system.</returns> + [HttpGet("Info")] + [Authorize(Policy = Policies.FirstTimeSetupOrIgnoreParentalControl)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public ActionResult<SystemInfo> GetSystemInfo() + => _systemManager.GetSystemInfo(Request); + + /// <summary> + /// Gets information about the server. + /// </summary> + /// <response code="200">Information retrieved.</response> + /// <response code="403">User does not have permission to retrieve information.</response> + /// <returns>A <see cref="SystemInfo"/> with info about the system.</returns> + [HttpGet("Info/Storage")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public ActionResult<SystemStorageDto> GetSystemStorage() + => Ok(SystemStorageDto.FromSystemStorageInfo(_systemManager.GetSystemStorageInfo())); + + /// <summary> + /// Gets public information about the server. + /// </summary> + /// <response code="200">Information retrieved.</response> + /// <returns>A <see cref="PublicSystemInfo"/> with public info about the system.</returns> + [HttpGet("Info/Public")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<PublicSystemInfo> GetPublicSystemInfo() + => _systemManager.GetPublicSystemInfo(Request); + + /// <summary> + /// Pings the system. + /// </summary> + /// <response code="200">Information retrieved.</response> + /// <returns>The server name.</returns> + [HttpGet("Ping", Name = "GetPingSystem")] + [HttpPost("Ping", Name = "PostPingSystem")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<string> PingSystem() + => _appHost.Name; + + /// <summary> + /// Restarts the application. + /// </summary> + /// <response code="204">Server restarted.</response> + /// <response code="403">User does not have permission to restart server.</response> + /// <returns>No content. Server restarted.</returns> + [HttpPost("Restart")] + [Authorize(Policy = Policies.LocalAccessOrRequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public ActionResult RestartApplication() + { + _systemManager.Restart(); + return NoContent(); + } + + /// <summary> + /// Shuts down the application. + /// </summary> + /// <response code="204">Server shut down.</response> + /// <response code="403">User does not have permission to shutdown server.</response> + /// <returns>No content. Server shut down.</returns> + [HttpPost("Shutdown")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public ActionResult ShutdownApplication() + { + _systemManager.Shutdown(); + return NoContent(); + } + + /// <summary> + /// Gets a list of available server log files. + /// </summary> + /// <response code="200">Information retrieved.</response> + /// <response code="403">User does not have permission to get server logs.</response> + /// <returns>An array of <see cref="LogFile"/> with the available log files.</returns> + [HttpGet("Logs")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public ActionResult<LogFile[]> GetServerLogs() + { + IEnumerable<FileSystemMetadata> files; + + try + { + files = _fileSystem.GetFiles(_appPaths.LogDirectoryPath, new[] { ".txt", ".log" }, true, false); + } + catch (IOException ex) + { + _logger.LogError(ex, "Error getting logs"); + files = Enumerable.Empty<FileSystemMetadata>(); + } + + var result = files.Select(i => new LogFile + { + DateCreated = _fileSystem.GetCreationTimeUtc(i), + DateModified = _fileSystem.GetLastWriteTimeUtc(i), + Name = i.Name, + Size = i.Length + }) + .OrderByDescending(i => i.DateModified) + .ThenByDescending(i => i.DateCreated) + .ThenBy(i => i.Name) + .ToArray(); + + return result; + } + + /// <summary> + /// Gets information about the request endpoint. + /// </summary> + /// <response code="200">Information retrieved.</response> + /// <response code="403">User does not have permission to get endpoint information.</response> + /// <returns><see cref="EndPointInfo"/> with information about the endpoint.</returns> + [HttpGet("Endpoint")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public ActionResult<EndPointInfo> GetEndpointInfo() + { + return new EndPointInfo + { + IsLocal = HttpContext.IsLocal(), + IsInNetwork = _networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP()) + }; + } + + /// <summary> + /// Gets a log file. + /// </summary> + /// <param name="name">The name of the log file to get.</param> + /// <response code="200">Log file retrieved.</response> + /// <response code="403">User does not have permission to get log files.</response> + /// <response code="404">Could not find a log file with the name.</response> + /// <returns>The log file.</returns> + [HttpGet("Logs/Log")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesFile(MediaTypeNames.Text.Plain)] + public ActionResult GetLogFile([FromQuery, Required] string name) + { + var file = _fileSystem + .GetFiles(_appPaths.LogDirectoryPath) + .FirstOrDefault(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase)); + + if (file is null) + { + return NotFound("Log file not found."); + } + + // For older files, assume fully static + var fileShare = file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1) ? FileShare.Read : FileShare.ReadWrite; + FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, fileShare, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); + return File(stream, "text/plain; charset=utf-8"); + } +} diff --git a/Jellyfin.Api/Controllers/TimeSyncController.cs b/Jellyfin.Api/Controllers/TimeSyncController.cs new file mode 100644 index 00000000..d7304cf4 --- /dev/null +++ b/Jellyfin.Api/Controllers/TimeSyncController.cs @@ -0,0 +1,33 @@ +using System; +using MediaBrowser.Model.SyncPlay; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The time sync controller. +/// </summary> +[Route("")] +public class TimeSyncController : BaseJellyfinApiController +{ + /// <summary> + /// Gets the current UTC time. + /// </summary> + /// <response code="200">Time returned.</response> + /// <returns>An <see cref="UtcTimeResponse"/> to sync the client and server time.</returns> + [HttpGet("GetUtcTime")] + [ProducesResponseType(statusCode: StatusCodes.Status200OK)] + public ActionResult<UtcTimeResponse> GetUtcTime() + { + // Important to keep the following line at the beginning + var requestReceptionTime = DateTime.UtcNow; + + // Important to keep the following line at the end + var responseTransmissionTime = DateTime.UtcNow; + + // Implementing NTP on such a high level results in this useless + // information being sent. On the other hand it enables future additions. + return new UtcTimeResponse(requestReceptionTime, responseTransmissionTime); + } +} diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs new file mode 100644 index 00000000..3e4bac89 --- /dev/null +++ b/Jellyfin.Api/Controllers/TrailersController.cs @@ -0,0 +1,298 @@ +using System; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The trailers controller. +/// </summary> +[Authorize] +public class TrailersController : BaseJellyfinApiController +{ + private readonly ItemsController _itemsController; + + /// <summary> + /// Initializes a new instance of the <see cref="TrailersController"/> class. + /// </summary> + /// <param name="itemsController">Instance of <see cref="ItemsController"/>.</param> + public TrailersController(ItemsController itemsController) + { + _itemsController = itemsController; + } + + /// <summary> + /// Finds movies and trailers similar to a given trailer. + /// </summary> + /// <param name="userId">The user id supplied as query parameter; this is required when not using an API key.</param> + /// <param name="maxOfficialRating">Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).</param> + /// <param name="hasThemeSong">Optional filter by items with theme songs.</param> + /// <param name="hasThemeVideo">Optional filter by items with theme videos.</param> + /// <param name="hasSubtitles">Optional filter by items with subtitles.</param> + /// <param name="hasSpecialFeature">Optional filter by items with special features.</param> + /// <param name="hasTrailer">Optional filter by items with trailers.</param> + /// <param name="adjacentTo">Optional. Return items that are siblings of a supplied item.</param> + /// <param name="parentIndexNumber">Optional filter by parent index number.</param> + /// <param name="hasParentalRating">Optional filter by items that have or do not have a parental rating.</param> + /// <param name="isHd">Optional filter by items that are HD or not.</param> + /// <param name="is4K">Optional filter by items that are 4K or not.</param> + /// <param name="locationTypes">Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimited.</param> + /// <param name="excludeLocationTypes">Optional. If specified, results will be filtered based on the LocationType. This allows multiple, comma delimited.</param> + /// <param name="isMissing">Optional filter by items that are missing episodes or not.</param> + /// <param name="isUnaired">Optional filter by items that are unaired episodes or not.</param> + /// <param name="minCommunityRating">Optional filter by minimum community rating.</param> + /// <param name="minCriticRating">Optional filter by minimum critic rating.</param> + /// <param name="minPremiereDate">Optional. The minimum premiere date. Format = ISO.</param> + /// <param name="minDateLastSaved">Optional. The minimum last saved date. Format = ISO.</param> + /// <param name="minDateLastSavedForUser">Optional. The minimum last saved date for the current user. Format = ISO.</param> + /// <param name="maxPremiereDate">Optional. The maximum premiere date. Format = ISO.</param> + /// <param name="hasOverview">Optional filter by items that have an overview or not.</param> + /// <param name="hasImdbId">Optional filter by items that have an IMDb id or not.</param> + /// <param name="hasTmdbId">Optional filter by items that have a TMDb id or not.</param> + /// <param name="hasTvdbId">Optional filter by items that have a TVDb id or not.</param> + /// <param name="isMovie">Optional filter for live tv movies.</param> + /// <param name="isSeries">Optional filter for live tv series.</param> + /// <param name="isNews">Optional filter for live tv news.</param> + /// <param name="isKids">Optional filter for live tv kids.</param> + /// <param name="isSports">Optional filter for live tv sports.</param> + /// <param name="excludeItemIds">Optional. If specified, results will be filtered by excluding item ids. This allows multiple, comma delimited.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="recursive">When searching within folders, this determines whether or not the search will be recursive. true/false.</param> + /// <param name="searchTerm">Optional. Filter based on a search term.</param> + /// <param name="sortOrder">Sort Order - Ascending, Descending.</param> + /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param> + /// <param name="excludeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param> + /// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</param> + /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param> + /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param> + /// <param name="imageTypes">Optional. If specified, results will be filtered based on those containing image types. This allows multiple, comma delimited.</param> + /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param> + /// <param name="isPlayed">Optional filter by items that are played, or not.</param> + /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param> + /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.</param> + /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.</param> + /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.</param> + /// <param name="enableUserData">Optional, include user data.</param> + /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="person">Optional. If specified, results will be filtered to include only those containing the specified person.</param> + /// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the specified person id.</param> + /// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.</param> + /// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.</param> + /// <param name="artists">Optional. If specified, results will be filtered based on artists. This allows multiple, pipe delimited.</param> + /// <param name="excludeArtistIds">Optional. If specified, results will be filtered based on artist id. This allows multiple, pipe delimited.</param> + /// <param name="artistIds">Optional. If specified, results will be filtered to include only those containing the specified artist id.</param> + /// <param name="albumArtistIds">Optional. If specified, results will be filtered to include only those containing the specified album artist id.</param> + /// <param name="contributingArtistIds">Optional. If specified, results will be filtered to include only those containing the specified contributing artist id.</param> + /// <param name="albums">Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimited.</param> + /// <param name="albumIds">Optional. If specified, results will be filtered based on album id. This allows multiple, pipe delimited.</param> + /// <param name="ids">Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited.</param> + /// <param name="videoTypes">Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimited.</param> + /// <param name="minOfficialRating">Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).</param> + /// <param name="isLocked">Optional filter by items that are locked.</param> + /// <param name="isPlaceHolder">Optional filter by items that are placeholders.</param> + /// <param name="hasOfficialRating">Optional filter by items that have official ratings.</param> + /// <param name="collapseBoxSetItems">Whether or not to hide items behind their boxsets.</param> + /// <param name="minWidth">Optional. Filter by the minimum width of the item.</param> + /// <param name="minHeight">Optional. Filter by the minimum height of the item.</param> + /// <param name="maxWidth">Optional. Filter by the maximum width of the item.</param> + /// <param name="maxHeight">Optional. Filter by the maximum height of the item.</param> + /// <param name="is3D">Optional filter by items that are 3D, or not.</param> + /// <param name="seriesStatus">Optional filter by Series Status. Allows multiple, comma delimited.</param> + /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param> + /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param> + /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param> + /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.</param> + /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.</param> + /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param> + /// <param name="enableImages">Optional, include image information in output.</param> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the trailers.</returns> + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetTrailers( + [FromQuery] Guid? userId, + [FromQuery] string? maxOfficialRating, + [FromQuery] bool? hasThemeSong, + [FromQuery] bool? hasThemeVideo, + [FromQuery] bool? hasSubtitles, + [FromQuery] bool? hasSpecialFeature, + [FromQuery] bool? hasTrailer, + [FromQuery] Guid? adjacentTo, + [FromQuery] int? parentIndexNumber, + [FromQuery] bool? hasParentalRating, + [FromQuery] bool? isHd, + [FromQuery] bool? is4K, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] locationTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] excludeLocationTypes, + [FromQuery] bool? isMissing, + [FromQuery] bool? isUnaired, + [FromQuery] double? minCommunityRating, + [FromQuery] double? minCriticRating, + [FromQuery] DateTime? minPremiereDate, + [FromQuery] DateTime? minDateLastSaved, + [FromQuery] DateTime? minDateLastSavedForUser, + [FromQuery] DateTime? maxPremiereDate, + [FromQuery] bool? hasOverview, + [FromQuery] bool? hasImdbId, + [FromQuery] bool? hasTmdbId, + [FromQuery] bool? hasTvdbId, + [FromQuery] bool? isMovie, + [FromQuery] bool? isSeries, + [FromQuery] bool? isNews, + [FromQuery] bool? isKids, + [FromQuery] bool? isSports, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeItemIds, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] bool? recursive, + [FromQuery] string? searchTerm, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, + [FromQuery] Guid? parentId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters, + [FromQuery] bool? isFavorite, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] imageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, + [FromQuery] bool? isPlayed, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery] string? person, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] studios, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] artists, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] artistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumArtistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] contributingArtistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] albums, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] VideoType[] videoTypes, + [FromQuery] string? minOfficialRating, + [FromQuery] bool? isLocked, + [FromQuery] bool? isPlaceHolder, + [FromQuery] bool? hasOfficialRating, + [FromQuery] bool? collapseBoxSetItems, + [FromQuery] int? minWidth, + [FromQuery] int? minHeight, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] bool? is3D, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SeriesStatus[] seriesStatus, + [FromQuery] string? nameStartsWithOrGreater, + [FromQuery] string? nameStartsWith, + [FromQuery] string? nameLessThan, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds, + [FromQuery] bool enableTotalRecordCount = true, + [FromQuery] bool? enableImages = true) + { + var includeItemTypes = new[] { BaseItemKind.Trailer }; + + return _itemsController + .GetItems( + userId, + maxOfficialRating, + hasThemeSong, + hasThemeVideo, + hasSubtitles, + hasSpecialFeature, + hasTrailer, + adjacentTo, + null, + parentIndexNumber, + hasParentalRating, + isHd, + is4K, + locationTypes, + excludeLocationTypes, + isMissing, + isUnaired, + minCommunityRating, + minCriticRating, + minPremiereDate, + minDateLastSaved, + minDateLastSavedForUser, + maxPremiereDate, + hasOverview, + hasImdbId, + hasTmdbId, + hasTvdbId, + isMovie, + isSeries, + isNews, + isKids, + isSports, + excludeItemIds, + startIndex, + limit, + recursive, + searchTerm, + sortOrder, + parentId, + fields, + excludeItemTypes, + includeItemTypes, + filters, + isFavorite, + mediaTypes, + imageTypes, + sortBy, + isPlayed, + genres, + officialRatings, + tags, + years, + enableUserData, + imageTypeLimit, + enableImageTypes, + person, + personIds, + personTypes, + studios, + artists, + excludeArtistIds, + artistIds, + albumArtistIds, + contributingArtistIds, + albums, + albumIds, + ids, + videoTypes, + minOfficialRating, + isLocked, + isPlaceHolder, + hasOfficialRating, + collapseBoxSetItems, + minWidth, + minHeight, + maxWidth, + maxHeight, + is3D, + seriesStatus, + nameStartsWithOrGreater, + nameStartsWith, + nameLessThan, + studioIds, + genreIds, + enableTotalRecordCount, + enableImages); + } +} diff --git a/Jellyfin.Api/Controllers/TrickplayController.cs b/Jellyfin.Api/Controllers/TrickplayController.cs new file mode 100644 index 00000000..c9f8b367 --- /dev/null +++ b/Jellyfin.Api/Controllers/TrickplayController.cs @@ -0,0 +1,105 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Net.Mime; +using System.Text; +using System.Threading.Tasks; +using Jellyfin.Api.Attributes; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Trickplay; +using MediaBrowser.Model; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Trickplay controller. +/// </summary> +[Route("")] +[Authorize] +public class TrickplayController : BaseJellyfinApiController +{ + private readonly ILibraryManager _libraryManager; + private readonly ITrickplayManager _trickplayManager; + + /// <summary> + /// Initializes a new instance of the <see cref="TrickplayController"/> class. + /// </summary> + /// <param name="libraryManager">Instance of <see cref="ILibraryManager"/>.</param> + /// <param name="trickplayManager">Instance of <see cref="ITrickplayManager"/>.</param> + public TrickplayController( + ILibraryManager libraryManager, + ITrickplayManager trickplayManager) + { + _libraryManager = libraryManager; + _trickplayManager = trickplayManager; + } + + /// <summary> + /// Gets an image tiles playlist for trickplay. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="width">The width of a single tile.</param> + /// <param name="mediaSourceId">The media version id, if using an alternate version.</param> + /// <response code="200">Tiles playlist returned.</response> + /// <returns>A <see cref="FileResult"/> containing the trickplay playlist file.</returns> + [HttpGet("Videos/{itemId}/Trickplay/{width}/tiles.m3u8")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesPlaylistFile] + public async Task<ActionResult> GetTrickplayHlsPlaylist( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] int width, + [FromQuery] Guid? mediaSourceId) + { + string? playlist = await _trickplayManager.GetHlsPlaylist(mediaSourceId ?? itemId, width, User.GetToken()).ConfigureAwait(false); + + if (string.IsNullOrEmpty(playlist)) + { + return NotFound(); + } + + return Content(playlist, MimeTypes.GetMimeType("playlist.m3u8"), Encoding.UTF8); + } + + /// <summary> + /// Gets a trickplay tile image. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="width">The width of a single tile.</param> + /// <param name="index">The index of the desired tile.</param> + /// <param name="mediaSourceId">The media version id, if using an alternate version.</param> + /// <response code="200">Tile image returned.</response> + /// <response code="200">Tile image not found at specified index.</response> + /// <returns>A <see cref="FileResult"/> containing the trickplay tiles image.</returns> + [HttpGet("Videos/{itemId}/Trickplay/{width}/{index}.jpg")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + public async Task<ActionResult> GetTrickplayTileImage( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] int width, + [FromRoute, Required] int index, + [FromQuery] Guid? mediaSourceId) + { + var item = _libraryManager.GetItemById<BaseItem>(mediaSourceId ?? itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + var saveWithMedia = _libraryManager.GetLibraryOptions(item).SaveTrickplayWithMedia; + var path = await _trickplayManager.GetTrickplayTilePathAsync(item, width, index, saveWithMedia).ConfigureAwait(false); + if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path)) + { + Response.Headers.ContentDisposition = "attachment"; + return PhysicalFile(path, MediaTypeNames.Image.Jpeg); + } + + return NotFound(); + } +} diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs new file mode 100644 index 00000000..c86c9b8f --- /dev/null +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -0,0 +1,387 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.TV; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The tv shows controller. +/// </summary> +[Route("Shows")] +[Authorize] +public class TvShowsController : BaseJellyfinApiController +{ + private readonly IUserManager _userManager; + private readonly ILibraryManager _libraryManager; + private readonly IDtoService _dtoService; + private readonly ITVSeriesManager _tvSeriesManager; + + /// <summary> + /// Initializes a new instance of the <see cref="TvShowsController"/> class. + /// </summary> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> + /// <param name="tvSeriesManager">Instance of the <see cref="ITVSeriesManager"/> interface.</param> + public TvShowsController( + IUserManager userManager, + ILibraryManager libraryManager, + IDtoService dtoService, + ITVSeriesManager tvSeriesManager) + { + _userManager = userManager; + _libraryManager = libraryManager; + _dtoService = dtoService; + _tvSeriesManager = tvSeriesManager; + } + + /// <summary> + /// Gets a list of next up episodes. + /// </summary> + /// <param name="userId">The user id of the user to get the next up episodes for.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="seriesId">Optional. Filter by series id.</param> + /// <param name="parentId">Optional. Specify this to localize the search to a specific item or folder. Omit to use the root.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <param name="nextUpDateCutoff">Optional. Starting date of shows to show in Next Up section.</param> + /// <param name="enableTotalRecordCount">Whether to enable the total records count. Defaults to true.</param> + /// <param name="enableResumable">Whether to include resumable episodes in next up results.</param> + /// <param name="enableRewatching">Whether to include watched episodes in next up results.</param> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the next up episodes.</returns> + [HttpGet("NextUp")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetNextUp( + [FromQuery] Guid? userId, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] Guid? seriesId, + [FromQuery] Guid? parentId, + [FromQuery] bool? enableImages, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery] bool? enableUserData, + [FromQuery] DateTime? nextUpDateCutoff, + [FromQuery] bool enableTotalRecordCount = true, + [FromQuery] bool enableResumable = true, + [FromQuery] bool enableRewatching = false) + { + var user = _userManager.GetUserById(RequestHelpers.GetUserId(User, userId)); + if (user is null) + { + return NotFound(); + } + + var options = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + + var result = _tvSeriesManager.GetNextUp( + new NextUpQuery + { + Limit = limit, + ParentId = parentId, + SeriesId = seriesId, + StartIndex = startIndex, + User = user, + EnableTotalRecordCount = enableTotalRecordCount, + NextUpDateCutoff = nextUpDateCutoff ?? DateTime.MinValue, + EnableResumable = enableResumable, + EnableRewatching = enableRewatching + }, + options); + + var returnItems = _dtoService.GetBaseItemDtos(result.Items, options, user); + + return new QueryResult<BaseItemDto>( + startIndex, + result.TotalRecordCount, + returnItems); + } + + /// <summary> + /// Gets a list of upcoming episodes. + /// </summary> + /// <param name="userId">The user id of the user to get the upcoming episodes for.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="parentId">Optional. Specify this to localize the search to a specific item or folder. Omit to use the root.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the upcoming episodes.</returns> + [HttpGet("Upcoming")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetUpcomingEpisodes( + [FromQuery] Guid? userId, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] Guid? parentId, + [FromQuery] bool? enableImages, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery] bool? enableUserData) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + + var minPremiereDate = DateTime.UtcNow.Date.AddDays(-1); + + var parentIdGuid = parentId ?? Guid.Empty; + + var options = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + + var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user) + { + IncludeItemTypes = new[] { BaseItemKind.Episode }, + OrderBy = new[] { (ItemSortBy.PremiereDate, SortOrder.Ascending), (ItemSortBy.SortName, SortOrder.Ascending) }, + MinPremiereDate = minPremiereDate, + StartIndex = startIndex, + Limit = limit, + ParentId = parentIdGuid, + Recursive = true, + DtoOptions = options + }); + + var returnItems = _dtoService.GetBaseItemDtos(itemsResult, options, user); + + return new QueryResult<BaseItemDto>( + startIndex, + itemsResult.Count, + returnItems); + } + + /// <summary> + /// Gets episodes for a tv season. + /// </summary> + /// <param name="seriesId">The series id.</param> + /// <param name="userId">The user id.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param> + /// <param name="season">Optional filter by season number.</param> + /// <param name="seasonId">Optional. Filter by season id.</param> + /// <param name="isMissing">Optional. Filter by items that are missing episodes or not.</param> + /// <param name="adjacentTo">Optional. Return items that are siblings of a supplied item.</param> + /// <param name="startItemId">Optional. Skip through the list until a given item is found.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="enableImages">Optional, include image information in output.</param> + /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the episodes on success or a <see cref="NotFoundResult"/> if the series was not found.</returns> + [HttpGet("{seriesId}/Episodes")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<QueryResult<BaseItemDto>> GetEpisodes( + [FromRoute, Required] Guid seriesId, + [FromQuery] Guid? userId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] int? season, + [FromQuery] Guid? seasonId, + [FromQuery] bool? isMissing, + [FromQuery] Guid? adjacentTo, + [FromQuery] Guid? startItemId, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] bool? enableImages, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery] bool? enableUserData, + [FromQuery] ItemSortBy? sortBy) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + + List<BaseItem> episodes; + + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + var shouldIncludeMissingEpisodes = (user is not null && user.DisplayMissingEpisodes) || User.GetIsApiKey(); + + if (seasonId.HasValue) // Season id was supplied. Get episodes by season id. + { + var item = _libraryManager.GetItemById<BaseItem>(seasonId.Value); + if (item is not Season seasonItem) + { + return NotFound("No season exists with Id " + seasonId); + } + + episodes = seasonItem.GetEpisodes(user, dtoOptions, shouldIncludeMissingEpisodes); + } + else if (season.HasValue) // Season number was supplied. Get episodes by season number + { + var series = _libraryManager.GetItemById<Series>(seriesId); + if (series is null) + { + return NotFound("Series not found"); + } + + var seasonItem = series + .GetSeasons(user, dtoOptions) + .FirstOrDefault(i => i.IndexNumber == season.Value); + + episodes = seasonItem is null ? + new List<BaseItem>() + : ((Season)seasonItem).GetEpisodes(user, dtoOptions, shouldIncludeMissingEpisodes); + } + else // No season number or season id was supplied. Returning all episodes. + { + if (_libraryManager.GetItemById<BaseItem>(seriesId) is not Series series) + { + return NotFound("Series not found"); + } + + episodes = series.GetEpisodes(user, dtoOptions, shouldIncludeMissingEpisodes).ToList(); + } + + // Filter after the fact in case the ui doesn't want them + if (isMissing.HasValue) + { + var val = isMissing.Value; + episodes = episodes + .Where(i => ((Episode)i).IsMissingEpisode == val) + .ToList(); + } + + if (startItemId.HasValue) + { + episodes = episodes + .SkipWhile(i => !startItemId.Value.Equals(i.Id)) + .ToList(); + } + + // This must be the last filter + if (!adjacentTo.IsNullOrEmpty()) + { + episodes = UserViewBuilder.FilterForAdjacency(episodes, adjacentTo.Value).ToList(); + } + + if (sortBy == ItemSortBy.Random) + { + episodes.Shuffle(); + } + + var returnItems = episodes; + + if (startIndex.HasValue || limit.HasValue) + { + returnItems = ApplyPaging(episodes, startIndex, limit).ToList(); + } + + var dtos = _dtoService.GetBaseItemDtos(returnItems, dtoOptions, user); + + return new QueryResult<BaseItemDto>( + startIndex, + episodes.Count, + dtos); + } + + /// <summary> + /// Gets seasons for a tv series. + /// </summary> + /// <param name="seriesId">The series id.</param> + /// <param name="userId">The user id.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param> + /// <param name="isSpecialSeason">Optional. Filter by special season.</param> + /// <param name="isMissing">Optional. Filter by items that are missing episodes or not.</param> + /// <param name="adjacentTo">Optional. Return items that are siblings of a supplied item.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> on success or a <see cref="NotFoundResult"/> if the series was not found.</returns> + [HttpGet("{seriesId}/Seasons")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<QueryResult<BaseItemDto>> GetSeasons( + [FromRoute, Required] Guid seriesId, + [FromQuery] Guid? userId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery] bool? isSpecialSeason, + [FromQuery] bool? isMissing, + [FromQuery] Guid? adjacentTo, + [FromQuery] bool? enableImages, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery] bool? enableUserData) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var item = _libraryManager.GetItemById<Series>(seriesId, user); + if (item is null) + { + return NotFound(); + } + + var seasons = item.GetItemList(new InternalItemsQuery(user) + { + IsMissing = isMissing, + IsSpecialSeason = isSpecialSeason, + AdjacentTo = adjacentTo + }); + + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + + var returnItems = _dtoService.GetBaseItemDtos(seasons, dtoOptions, user); + + return new QueryResult<BaseItemDto>(returnItems); + } + + /// <summary> + /// Applies the paging. + /// </summary> + /// <param name="items">The items.</param> + /// <param name="startIndex">The start index.</param> + /// <param name="limit">The limit.</param> + /// <returns>IEnumerable{BaseItem}.</returns> + private IEnumerable<BaseItem> ApplyPaging(IEnumerable<BaseItem> items, int? startIndex, int? limit) + { + // Start at + if (startIndex.HasValue) + { + items = items.Skip(startIndex.Value); + } + + // Return limit + if (limit.HasValue) + { + items = items.Take(limit.Value); + } + + return items; + } +} diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs new file mode 100644 index 00000000..b1a91ae7 --- /dev/null +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -0,0 +1,358 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Api.Attributes; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Api.Models.StreamingDtos; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Session; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The universal audio controller. +/// </summary> +[Route("")] +public class UniversalAudioController : BaseJellyfinApiController +{ + private readonly ILibraryManager _libraryManager; + private readonly ILogger<UniversalAudioController> _logger; + private readonly MediaInfoHelper _mediaInfoHelper; + private readonly AudioHelper _audioHelper; + private readonly DynamicHlsHelper _dynamicHlsHelper; + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="UniversalAudioController"/> class. + /// </summary> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="logger">Instance of the <see cref="ILogger{UniversalAudioController}"/> interface.</param> + /// <param name="mediaInfoHelper">Instance of <see cref="MediaInfoHelper"/>.</param> + /// <param name="audioHelper">Instance of <see cref="AudioHelper"/>.</param> + /// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + public UniversalAudioController( + ILibraryManager libraryManager, + ILogger<UniversalAudioController> logger, + MediaInfoHelper mediaInfoHelper, + AudioHelper audioHelper, + DynamicHlsHelper dynamicHlsHelper, + IUserManager userManager) + { + _libraryManager = libraryManager; + _logger = logger; + _mediaInfoHelper = mediaInfoHelper; + _audioHelper = audioHelper; + _dynamicHlsHelper = dynamicHlsHelper; + _userManager = userManager; + } + + /// <summary> + /// Gets an audio stream. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="container">Optional. The audio container.</param> + /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> + /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> + /// <param name="userId">Optional. The user id.</param> + /// <param name="audioCodec">Optional. The audio codec to transcode to.</param> + /// <param name="maxAudioChannels">Optional. The maximum number of audio channels.</param> + /// <param name="transcodingAudioChannels">Optional. The number of how many audio channels to transcode to.</param> + /// <param name="maxStreamingBitrate">Optional. The maximum streaming bitrate.</param> + /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param> + /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param> + /// <param name="transcodingContainer">Optional. The container to transcode to.</param> + /// <param name="transcodingProtocol">Optional. The transcoding protocol.</param> + /// <param name="maxAudioSampleRate">Optional. The maximum audio sample rate.</param> + /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param> + /// <param name="enableRemoteMedia">Optional. Whether to enable remote media.</param> + /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param> + /// <param name="enableRedirection">Whether to enable redirection. Defaults to true.</param> + /// <response code="200">Audio stream returned.</response> + /// <response code="302">Redirected to remote audio stream.</response> + /// <response code="404">Item not found.</response> + /// <returns>A <see cref="Task"/> containing the audio file.</returns> + [HttpGet("Audio/{itemId}/universal")] + [HttpHead("Audio/{itemId}/universal", Name = "HeadUniversalAudioStream")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status302Found)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesAudioFile] + public async Task<ActionResult> GetUniversalAudioStream( + [FromRoute, Required] Guid itemId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] container, + [FromQuery] string? mediaSourceId, + [FromQuery] string? deviceId, + [FromQuery] Guid? userId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec, + [FromQuery] int? maxAudioChannels, + [FromQuery] int? transcodingAudioChannels, + [FromQuery] int? maxStreamingBitrate, + [FromQuery] int? audioBitRate, + [FromQuery] long? startTimeTicks, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? transcodingContainer, + [FromQuery] MediaStreamProtocol? transcodingProtocol, + [FromQuery] int? maxAudioSampleRate, + [FromQuery] int? maxAudioBitDepth, + [FromQuery] bool? enableRemoteMedia, + [FromQuery] bool enableAudioVbrEncoding = true, + [FromQuery] bool enableRedirection = true) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var item = _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels); + + _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", deviceProfile); + + var info = await _mediaInfoHelper.GetPlaybackInfo( + item, + user, + mediaSourceId) + .ConfigureAwait(false); + + // set device specific data + foreach (var sourceInfo in info.MediaSources) + { + sourceInfo.TranscodingContainer = transcodingContainer; + sourceInfo.TranscodingSubProtocol = transcodingProtocol ?? sourceInfo.TranscodingSubProtocol; + _mediaInfoHelper.SetDeviceSpecificData( + item, + sourceInfo, + deviceProfile, + User, + maxStreamingBitrate ?? deviceProfile.MaxStreamingBitrate, + startTimeTicks ?? 0, + mediaSourceId ?? string.Empty, + null, + null, + maxAudioChannels, + info.PlaySessionId!, + userId ?? Guid.Empty, + true, + true, + true, + true, + true, + false, + Request.HttpContext.GetNormalizedRemoteIP()); + } + + _mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate); + + foreach (var source in info.MediaSources) + { + _mediaInfoHelper.NormalizeMediaSourceContainer(source, deviceProfile, DlnaProfileType.Video); + } + + var mediaSource = info.MediaSources[0]; + if (mediaSource.SupportsDirectPlay && mediaSource.Protocol == MediaProtocol.Http && enableRedirection && mediaSource.IsRemote && enableRemoteMedia.HasValue && enableRemoteMedia.Value) + { + return Redirect(mediaSource.Path); + } + + // This one is currently very misleading as the SupportsDirectStream actually means "can direct play" + // The definition of DirectStream also seems changed during development + var isStatic = mediaSource.SupportsDirectStream; + if (!isStatic && mediaSource.TranscodingSubProtocol == MediaStreamProtocol.hls) + { + // hls segment container can only be mpegts or fmp4 per ffmpeg documentation + // ffmpeg option -> file extension + // mpegts -> ts + // fmp4 -> mp4 + var supportedHlsContainers = new[] { "ts", "mp4" }; + + // fallback to mpegts if device reports some weird value unsupported by hls + var requestedSegmentContainer = Array.Exists( + supportedHlsContainers, + element => string.Equals(element, transcodingContainer, StringComparison.OrdinalIgnoreCase)) ? transcodingContainer : "ts"; + var segmentContainer = Array.Exists( + supportedHlsContainers, + element => string.Equals(element, mediaSource.TranscodingContainer, StringComparison.OrdinalIgnoreCase)) ? mediaSource.TranscodingContainer : requestedSegmentContainer; + var dynamicHlsRequestDto = new HlsAudioRequestDto + { + Id = itemId, + Container = ".m3u8", + Static = isStatic, + PlaySessionId = info.PlaySessionId, + SegmentContainer = segmentContainer, + MediaSourceId = mediaSourceId, + DeviceId = deviceId, + AudioCodec = mediaSource.TranscodeReasons == TranscodeReason.ContainerNotSupported ? "copy" : audioCodec, + EnableAutoStreamCopy = true, + AllowAudioStreamCopy = true, + AllowVideoStreamCopy = true, + AudioSampleRate = maxAudioSampleRate, + MaxAudioChannels = maxAudioChannels, + MaxAudioBitDepth = maxAudioBitDepth, + AudioBitRate = audioBitRate ?? maxStreamingBitrate, + StartTimeTicks = startTimeTicks, + SubtitleMethod = SubtitleDeliveryMethod.Hls, + RequireAvc = false, + DeInterlace = false, + RequireNonAnamorphic = false, + EnableMpegtsM2TsMode = false, + TranscodeReasons = mediaSource.TranscodeReasons == 0 ? null : mediaSource.TranscodeReasons.ToString(), + Context = EncodingContext.Static, + StreamOptions = new Dictionary<string, string>(), + EnableAdaptiveBitrateStreaming = false, + EnableAudioVbrEncoding = enableAudioVbrEncoding + }; + + return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType.Hls, dynamicHlsRequestDto, true) + .ConfigureAwait(false); + } + + var audioStreamingDto = new StreamingRequestDto + { + Id = itemId, + Container = isStatic ? null : ("." + mediaSource.TranscodingContainer), + Static = isStatic, + PlaySessionId = info.PlaySessionId, + MediaSourceId = mediaSourceId, + DeviceId = deviceId, + AudioCodec = audioCodec, + EnableAutoStreamCopy = true, + AllowAudioStreamCopy = true, + AllowVideoStreamCopy = true, + AudioSampleRate = maxAudioSampleRate, + MaxAudioChannels = maxAudioChannels, + AudioBitRate = isStatic ? null : (audioBitRate ?? maxStreamingBitrate), + MaxAudioBitDepth = maxAudioBitDepth, + AudioChannels = maxAudioChannels, + CopyTimestamps = true, + StartTimeTicks = startTimeTicks, + SubtitleMethod = SubtitleDeliveryMethod.Embed, + TranscodeReasons = mediaSource.TranscodeReasons == 0 ? null : mediaSource.TranscodeReasons.ToString(), + Context = EncodingContext.Static + }; + + return await _audioHelper.GetAudioStream(TranscodingJobType.Progressive, audioStreamingDto).ConfigureAwait(false); + } + + private DeviceProfile GetDeviceProfile( + string[] containers, + string? transcodingContainer, + string? audioCodec, + MediaStreamProtocol? transcodingProtocol, + int? transcodingAudioChannels, + int? maxAudioSampleRate, + int? maxAudioBitDepth, + int? maxAudioChannels) + { + var deviceProfile = new DeviceProfile(); + + int len = containers.Length; + var directPlayProfiles = new DirectPlayProfile[len]; + for (int i = 0; i < len; i++) + { + var parts = containers[i].Split('|', StringSplitOptions.RemoveEmptyEntries); + + var audioCodecs = parts.Length == 1 ? null : string.Join(',', parts.Skip(1)); + + directPlayProfiles[i] = new DirectPlayProfile + { + Type = DlnaProfileType.Audio, + Container = parts[0], + AudioCodec = audioCodecs + }; + } + + deviceProfile.DirectPlayProfiles = directPlayProfiles; + + deviceProfile.TranscodingProfiles = new[] + { + new TranscodingProfile + { + Type = DlnaProfileType.Audio, + Context = EncodingContext.Streaming, + Container = transcodingContainer ?? "mp3", + AudioCodec = audioCodec ?? "mp3", + Protocol = transcodingProtocol ?? MediaStreamProtocol.http, + MaxAudioChannels = transcodingAudioChannels?.ToString(CultureInfo.InvariantCulture) + } + }; + + var codecProfiles = new List<CodecProfile>(); + var conditions = new List<ProfileCondition>(); + + if (maxAudioSampleRate.HasValue) + { + // codec profile + conditions.Add( + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + IsRequired = false, + Property = ProfileConditionValue.AudioSampleRate, + Value = maxAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) + }); + } + + if (maxAudioBitDepth.HasValue) + { + // codec profile + conditions.Add( + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + IsRequired = false, + Property = ProfileConditionValue.AudioBitDepth, + Value = maxAudioBitDepth.Value.ToString(CultureInfo.InvariantCulture) + }); + } + + if (maxAudioChannels.HasValue) + { + // codec profile + conditions.Add( + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + IsRequired = false, + Property = ProfileConditionValue.AudioChannels, + Value = maxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) + }); + } + + if (conditions.Count > 0) + { + // codec profile + codecProfiles.Add( + new CodecProfile + { + Type = CodecType.Audio, + Container = string.Join(',', containers), + Conditions = conditions.ToArray() + }); + } + + deviceProfile.CodecProfiles = codecProfiles.ToArray(); + + return deviceProfile; + } +} diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs new file mode 100644 index 00000000..536b95db --- /dev/null +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -0,0 +1,636 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.Models.UserDtos; +using Jellyfin.Data; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Common.Api; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Playlists; +using MediaBrowser.Controller.QuickConnect; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Users; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// User controller. +/// </summary> +[Route("Users")] +public class UserController : BaseJellyfinApiController +{ + private readonly IUserManager _userManager; + private readonly ISessionManager _sessionManager; + private readonly INetworkManager _networkManager; + private readonly IDeviceManager _deviceManager; + private readonly IAuthorizationContext _authContext; + private readonly IServerConfigurationManager _config; + private readonly ILogger _logger; + private readonly IQuickConnect _quickConnectManager; + private readonly IPlaylistManager _playlistManager; + + /// <summary> + /// Initializes a new instance of the <see cref="UserController"/> class. + /// </summary> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param> + /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param> + /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param> + /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param> + /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="quickConnectManager">Instance of the <see cref="IQuickConnect"/> interface.</param> + /// <param name="playlistManager">Instance of the <see cref="IPlaylistManager"/> interface.</param> + public UserController( + IUserManager userManager, + ISessionManager sessionManager, + INetworkManager networkManager, + IDeviceManager deviceManager, + IAuthorizationContext authContext, + IServerConfigurationManager config, + ILogger<UserController> logger, + IQuickConnect quickConnectManager, + IPlaylistManager playlistManager) + { + _userManager = userManager; + _sessionManager = sessionManager; + _networkManager = networkManager; + _deviceManager = deviceManager; + _authContext = authContext; + _config = config; + _logger = logger; + _quickConnectManager = quickConnectManager; + _playlistManager = playlistManager; + } + + /// <summary> + /// Gets a list of users. + /// </summary> + /// <param name="isHidden">Optional filter by IsHidden=true or false.</param> + /// <param name="isDisabled">Optional filter by IsDisabled=true or false.</param> + /// <response code="200">Users returned.</response> + /// <returns>An <see cref="IEnumerable{UserDto}"/> containing the users.</returns> + [HttpGet] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<IEnumerable<UserDto>> GetUsers( + [FromQuery] bool? isHidden, + [FromQuery] bool? isDisabled) + { + var users = Get(isHidden, isDisabled, false, false); + return Ok(users); + } + + /// <summary> + /// Gets a list of publicly visible users for display on a login screen. + /// </summary> + /// <response code="200">Public users returned.</response> + /// <returns>An <see cref="IEnumerable{UserDto}"/> containing the public users.</returns> + [HttpGet("Public")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<IEnumerable<UserDto>> GetPublicUsers() + { + // If the startup wizard hasn't been completed then just return all users + if (!_config.Configuration.IsStartupWizardCompleted) + { + return Ok(Get(false, false, false, false)); + } + + return Ok(Get(false, false, true, true)); + } + + /// <summary> + /// Gets a user by Id. + /// </summary> + /// <param name="userId">The user id.</param> + /// <response code="200">User returned.</response> + /// <response code="404">User not found.</response> + /// <returns>An <see cref="UserDto"/> with information about the user or a <see cref="NotFoundResult"/> if the user was not found.</returns> + [HttpGet("{userId}")] + [Authorize(Policy = Policies.IgnoreParentalControl)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<UserDto> GetUserById([FromRoute, Required] Guid userId) + { + var user = _userManager.GetUserById(userId); + + if (user is null) + { + return NotFound("User not found"); + } + + var result = _userManager.GetUserDto(user, HttpContext.GetNormalizedRemoteIP().ToString()); + return result; + } + + /// <summary> + /// Deletes a user. + /// </summary> + /// <param name="userId">The user id.</param> + /// <response code="204">User deleted.</response> + /// <response code="404">User not found.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="NotFoundResult"/> if the user was not found.</returns> + [HttpDelete("{userId}")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> DeleteUser([FromRoute, Required] Guid userId) + { + var user = _userManager.GetUserById(userId); + if (user is null) + { + return NotFound(); + } + + await _sessionManager.RevokeUserTokens(user.Id, null).ConfigureAwait(false); + await _playlistManager.RemovePlaylistsAsync(userId).ConfigureAwait(false); + await _userManager.DeleteUserAsync(userId).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Authenticates a user. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="pw">The password as plain text.</param> + /// <response code="200">User authenticated.</response> + /// <response code="403">Sha1-hashed password only is not allowed.</response> + /// <response code="404">User not found.</response> + /// <returns>A <see cref="Task"/> containing an <see cref="AuthenticationResult"/>.</returns> + [HttpPost("{userId}/Authenticate")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ApiExplorerSettings(IgnoreApi = true)] + [Obsolete("Authenticate with username instead")] + public async Task<ActionResult<AuthenticationResult>> AuthenticateUser( + [FromRoute, Required] Guid userId, + [FromQuery, Required] string pw) + { + var user = _userManager.GetUserById(userId); + + if (user is null) + { + return NotFound("User not found"); + } + + AuthenticateUserByName request = new AuthenticateUserByName + { + Username = user.Username, + Pw = pw + }; + return await AuthenticateUserByName(request).ConfigureAwait(false); + } + + /// <summary> + /// Authenticates a user by name. + /// </summary> + /// <param name="request">The <see cref="AuthenticateUserByName"/> request.</param> + /// <response code="200">User authenticated.</response> + /// <returns>A <see cref="Task"/> containing an <see cref="AuthenticationRequest"/> with information about the new session.</returns> + [HttpPost("AuthenticateByName")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<AuthenticationResult>> AuthenticateUserByName([FromBody, Required] AuthenticateUserByName request) + { + var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); + + try + { + var result = await _sessionManager.AuthenticateNewSession(new AuthenticationRequest + { + App = auth.Client, + AppVersion = auth.Version, + DeviceId = auth.DeviceId, + DeviceName = auth.Device, + Password = request.Pw, + RemoteEndPoint = HttpContext.GetNormalizedRemoteIP().ToString(), + Username = request.Username + }).ConfigureAwait(false); + + return result; + } + catch (SecurityException e) + { + // rethrow adding IP address to message + throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIP()}] {e.Message}", e); + } + } + + /// <summary> + /// Authenticates a user with quick connect. + /// </summary> + /// <param name="request">The <see cref="QuickConnectDto"/> request.</param> + /// <response code="200">User authenticated.</response> + /// <response code="400">Missing token.</response> + /// <returns>A <see cref="Task"/> containing an <see cref="AuthenticationRequest"/> with information about the new session.</returns> + [HttpPost("AuthenticateWithQuickConnect")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<AuthenticationResult> AuthenticateWithQuickConnect([FromBody, Required] QuickConnectDto request) + { + try + { + return _quickConnectManager.GetAuthorizedRequest(request.Secret); + } + catch (SecurityException e) + { + // rethrow adding IP address to message + throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIP()}] {e.Message}", e); + } + } + + /// <summary> + /// Updates a user's password. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="request">The <see cref="UpdateUserPassword"/> request.</param> + /// <response code="204">Password successfully reset.</response> + /// <response code="403">User is not allowed to update the password.</response> + /// <response code="404">User not found.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="ForbidResult"/> or a <see cref="NotFoundResult"/> on failure.</returns> + [HttpPost("Password")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> UpdateUserPassword( + [FromQuery] Guid? userId, + [FromBody, Required] UpdateUserPassword request) + { + var requestUserId = userId ?? User.GetUserId(); + var user = _userManager.GetUserById(requestUserId); + if (user is null) + { + return NotFound(); + } + + if (!RequestHelpers.AssertCanUpdateUser(User, user, true)) + { + return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the password."); + } + + if (request.ResetPassword) + { + await _userManager.ResetPassword(user).ConfigureAwait(false); + } + else + { + if (!User.IsInRole(UserRoles.Administrator) || (userId.HasValue && User.GetUserId().Equals(userId.Value))) + { + var success = await _userManager.AuthenticateUser( + user.Username, + request.CurrentPw ?? string.Empty, + HttpContext.GetNormalizedRemoteIP().ToString(), + false).ConfigureAwait(false); + + if (success is null) + { + return StatusCode(StatusCodes.Status403Forbidden, "Invalid user or password entered."); + } + } + + await _userManager.ChangePassword(user, request.NewPw ?? string.Empty).ConfigureAwait(false); + + var currentToken = User.GetToken(); + + await _sessionManager.RevokeUserTokens(user.Id, currentToken).ConfigureAwait(false); + } + + return NoContent(); + } + + /// <summary> + /// Updates a user's password. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="request">The <see cref="UpdateUserPassword"/> request.</param> + /// <response code="204">Password successfully reset.</response> + /// <response code="403">User is not allowed to update the password.</response> + /// <response code="404">User not found.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="ForbidResult"/> or a <see cref="NotFoundResult"/> on failure.</returns> + [HttpPost("{userId}/Password")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public Task<ActionResult> UpdateUserPasswordLegacy( + [FromRoute, Required] Guid userId, + [FromBody, Required] UpdateUserPassword request) + => UpdateUserPassword(userId, request); + + /// <summary> + /// Updates a user. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="updateUser">The updated user model.</param> + /// <response code="204">User updated.</response> + /// <response code="400">User information was not supplied.</response> + /// <response code="403">User update forbidden.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="BadRequestResult"/> or a <see cref="ForbidResult"/> on failure.</returns> + [HttpPost] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task<ActionResult> UpdateUser( + [FromQuery] Guid? userId, + [FromBody, Required] UserDto updateUser) + { + var requestUserId = userId ?? User.GetUserId(); + var user = _userManager.GetUserById(requestUserId); + if (user is null) + { + return NotFound(); + } + + if (!RequestHelpers.AssertCanUpdateUser(User, user, true)) + { + return StatusCode(StatusCodes.Status403Forbidden, "User update not allowed."); + } + + if (!string.Equals(user.Username, updateUser.Name, StringComparison.Ordinal)) + { + await _userManager.RenameUser(user, updateUser.Name).ConfigureAwait(false); + } + + await _userManager.UpdateConfigurationAsync(requestUserId, updateUser.Configuration).ConfigureAwait(false); + + return NoContent(); + } + + /// <summary> + /// Updates a user. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="updateUser">The updated user model.</param> + /// <response code="204">User updated.</response> + /// <response code="400">User information was not supplied.</response> + /// <response code="403">User update forbidden.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="BadRequestResult"/> or a <see cref="ForbidResult"/> on failure.</returns> + [HttpPost("{userId}")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public Task<ActionResult> UpdateUserLegacy( + [FromRoute, Required] Guid userId, + [FromBody, Required] UserDto updateUser) + => UpdateUser(userId, updateUser); + + /// <summary> + /// Updates a user policy. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="newPolicy">The new user policy.</param> + /// <response code="204">User policy updated.</response> + /// <response code="400">User policy was not supplied.</response> + /// <response code="403">User policy update forbidden.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="BadRequestResult"/> or a <see cref="ForbidResult"/> on failure..</returns> + [HttpPost("{userId}/Policy")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task<ActionResult> UpdateUserPolicy( + [FromRoute, Required] Guid userId, + [FromBody, Required] UserPolicy newPolicy) + { + var user = _userManager.GetUserById(userId); + if (user is null) + { + return NotFound(); + } + + // If removing admin access + if (!newPolicy.IsAdministrator && user.HasPermission(PermissionKind.IsAdministrator)) + { + if (_userManager.Users.Count(i => i.HasPermission(PermissionKind.IsAdministrator)) == 1) + { + return StatusCode(StatusCodes.Status403Forbidden, "There must be at least one user in the system with administrative access."); + } + } + + // If disabling + if (newPolicy.IsDisabled && user.HasPermission(PermissionKind.IsAdministrator)) + { + return StatusCode(StatusCodes.Status403Forbidden, "Administrators cannot be disabled."); + } + + // If disabling + if (newPolicy.IsDisabled && !user.HasPermission(PermissionKind.IsDisabled)) + { + if (_userManager.Users.Count(i => !i.HasPermission(PermissionKind.IsDisabled)) == 1) + { + return StatusCode(StatusCodes.Status403Forbidden, "There must be at least one enabled user in the system."); + } + + var currentToken = User.GetToken(); + await _sessionManager.RevokeUserTokens(user.Id, currentToken).ConfigureAwait(false); + } + + await _userManager.UpdatePolicyAsync(userId, newPolicy).ConfigureAwait(false); + + return NoContent(); + } + + /// <summary> + /// Updates a user configuration. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="userConfig">The new user configuration.</param> + /// <response code="204">User configuration updated.</response> + /// <response code="403">User configuration update forbidden.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("Configuration")] + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task<ActionResult> UpdateUserConfiguration( + [FromQuery] Guid? userId, + [FromBody, Required] UserConfiguration userConfig) + { + var requestUserId = userId ?? User.GetUserId(); + var user = _userManager.GetUserById(requestUserId); + if (user is null) + { + return NotFound(); + } + + if (!RequestHelpers.AssertCanUpdateUser(User, user, true)) + { + return StatusCode(StatusCodes.Status403Forbidden, "User configuration update not allowed"); + } + + await _userManager.UpdateConfigurationAsync(requestUserId, userConfig).ConfigureAwait(false); + + return NoContent(); + } + + /// <summary> + /// Updates a user configuration. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="userConfig">The new user configuration.</param> + /// <response code="204">User configuration updated.</response> + /// <response code="403">User configuration update forbidden.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("{userId}/Configuration")] + [Authorize] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public Task<ActionResult> UpdateUserConfigurationLegacy( + [FromRoute, Required] Guid userId, + [FromBody, Required] UserConfiguration userConfig) + => UpdateUserConfiguration(userId, userConfig); + + /// <summary> + /// Creates a user. + /// </summary> + /// <param name="request">The create user by name request body.</param> + /// <response code="200">User created.</response> + /// <returns>An <see cref="UserDto"/> of the new user.</returns> + [HttpPost("New")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<UserDto>> CreateUserByName([FromBody, Required] CreateUserByName request) + { + var newUser = await _userManager.CreateUserAsync(request.Name).ConfigureAwait(false); + + // no need to authenticate password for new user + if (request.Password is not null) + { + await _userManager.ChangePassword(newUser, request.Password).ConfigureAwait(false); + } + + var result = _userManager.GetUserDto(newUser, HttpContext.GetNormalizedRemoteIP().ToString()); + + return result; + } + + /// <summary> + /// Initiates the forgot password process for a local user. + /// </summary> + /// <param name="forgotPasswordRequest">The forgot password request containing the entered username.</param> + /// <response code="200">Password reset process started.</response> + /// <returns>A <see cref="Task"/> containing a <see cref="ForgotPasswordResult"/>.</returns> + [HttpPost("ForgotPassword")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<ForgotPasswordResult>> ForgotPassword([FromBody, Required] ForgotPasswordDto forgotPasswordRequest) + { + var ip = HttpContext.GetNormalizedRemoteIP(); + var isLocal = HttpContext.IsLocal() + || _networkManager.IsInLocalNetwork(ip); + + if (!isLocal) + { + _logger.LogWarning("Password reset process initiated from outside the local network with IP: {IP}", ip); + } + + var result = await _userManager.StartForgotPasswordProcess(forgotPasswordRequest.EnteredUsername, isLocal).ConfigureAwait(false); + + return result; + } + + /// <summary> + /// Redeems a forgot password pin. + /// </summary> + /// <param name="forgotPasswordPinRequest">The forgot password pin request containing the entered pin.</param> + /// <response code="200">Pin reset process started.</response> + /// <returns>A <see cref="Task"/> containing a <see cref="PinRedeemResult"/>.</returns> + [HttpPost("ForgotPassword/Pin")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<PinRedeemResult>> ForgotPasswordPin([FromBody, Required] ForgotPasswordPinDto forgotPasswordPinRequest) + { + var result = await _userManager.RedeemPasswordResetPin(forgotPasswordPinRequest.Pin).ConfigureAwait(false); + return result; + } + + /// <summary> + /// Gets the user based on auth token. + /// </summary> + /// <response code="200">User returned.</response> + /// <response code="400">Token is not owned by a user.</response> + /// <returns>A <see cref="UserDto"/> for the authenticated user.</returns> + [HttpGet("Me")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public ActionResult<UserDto> GetCurrentUser() + { + var userId = User.GetUserId(); + if (userId.IsEmpty()) + { + return BadRequest(); + } + + var user = _userManager.GetUserById(userId); + if (user is null) + { + return BadRequest(); + } + + return _userManager.GetUserDto(user); + } + + private IEnumerable<UserDto> Get(bool? isHidden, bool? isDisabled, bool filterByDevice, bool filterByNetwork) + { + var users = _userManager.Users; + + if (isDisabled.HasValue) + { + users = users.Where(i => i.HasPermission(PermissionKind.IsDisabled) == isDisabled.Value); + } + + if (isHidden.HasValue) + { + users = users.Where(i => i.HasPermission(PermissionKind.IsHidden) == isHidden.Value); + } + + if (filterByDevice) + { + var deviceId = User.GetDeviceId(); + + if (!string.IsNullOrWhiteSpace(deviceId)) + { + users = users.Where(i => _deviceManager.CanAccessDevice(i, deviceId)); + } + } + + if (filterByNetwork) + { + if (!_networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP())) + { + users = users.Where(i => i.HasPermission(PermissionKind.EnableRemoteAccess)); + } + } + + var result = users + .OrderBy(u => u.Username) + .Select(i => _userManager.GetUserDto(i, HttpContext.GetNormalizedRemoteIP().ToString())); + + return result; + } +} diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs new file mode 100644 index 00000000..3ba7cc31 --- /dev/null +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -0,0 +1,696 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// User library controller. +/// </summary> +[Route("")] +[Authorize] +public class UserLibraryController : BaseJellyfinApiController +{ + private readonly IUserManager _userManager; + private readonly IUserDataManager _userDataRepository; + private readonly ILibraryManager _libraryManager; + private readonly IDtoService _dtoService; + private readonly IUserViewManager _userViewManager; + private readonly IFileSystem _fileSystem; + + /// <summary> + /// Initializes a new instance of the <see cref="UserLibraryController"/> class. + /// </summary> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="userDataRepository">Instance of the <see cref="IUserDataManager"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> + /// <param name="userViewManager">Instance of the <see cref="IUserViewManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + public UserLibraryController( + IUserManager userManager, + IUserDataManager userDataRepository, + ILibraryManager libraryManager, + IDtoService dtoService, + IUserViewManager userViewManager, + IFileSystem fileSystem) + { + _userManager = userManager; + _userDataRepository = userDataRepository; + _libraryManager = libraryManager; + _dtoService = dtoService; + _userViewManager = userViewManager; + _fileSystem = fileSystem; + } + + /// <summary> + /// Gets an item from a user's library. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <response code="200">Item returned.</response> + /// <returns>An <see cref="OkResult"/> containing the item.</returns> + [HttpGet("Items/{itemId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<BaseItemDto>> GetItem( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(userId.Value); + if (user is null) + { + return NotFound(); + } + + var item = itemId.IsEmpty() + ? _libraryManager.GetUserRootFolder() + : _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false); + + var dtoOptions = new DtoOptions(); + + return _dtoService.GetBaseItemDto(item, dtoOptions, user); + } + + /// <summary> + /// Gets an item from a user's library. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <response code="200">Item returned.</response> + /// <returns>An <see cref="OkResult"/> containing the item.</returns> + [HttpGet("Users/{userId}/Items/{itemId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public Task<ActionResult<BaseItemDto>> GetItemLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId) + => GetItem(userId, itemId); + + /// <summary> + /// Gets the root folder from a user's library. + /// </summary> + /// <param name="userId">User id.</param> + /// <response code="200">Root folder returned.</response> + /// <returns>An <see cref="OkResult"/> containing the user's root folder.</returns> + [HttpGet("Items/Root")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<BaseItemDto> GetRootFolder([FromQuery] Guid? userId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(userId.Value); + if (user is null) + { + return NotFound(); + } + + var item = _libraryManager.GetUserRootFolder(); + var dtoOptions = new DtoOptions(); + return _dtoService.GetBaseItemDto(item, dtoOptions, user); + } + + /// <summary> + /// Gets the root folder from a user's library. + /// </summary> + /// <param name="userId">User id.</param> + /// <response code="200">Root folder returned.</response> + /// <returns>An <see cref="OkResult"/> containing the user's root folder.</returns> + [HttpGet("Users/{userId}/Items/Root")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult<BaseItemDto> GetRootFolderLegacy( + [FromRoute, Required] Guid userId) + => GetRootFolder(userId); + + /// <summary> + /// Gets intros to play before the main media item plays. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <response code="200">Intros returned.</response> + /// <returns>An <see cref="OkResult"/> containing the intros to play.</returns> + [HttpGet("Items/{itemId}/Intros")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task<ActionResult<QueryResult<BaseItemDto>>> GetIntros( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(userId.Value); + if (user is null) + { + return NotFound(); + } + + var item = itemId.IsEmpty() + ? _libraryManager.GetUserRootFolder() + : _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false); + var dtoOptions = new DtoOptions(); + var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray(); + + return new QueryResult<BaseItemDto>(dtos); + } + + /// <summary> + /// Gets intros to play before the main media item plays. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <response code="200">Intros returned.</response> + /// <returns>An <see cref="OkResult"/> containing the intros to play.</returns> + [HttpGet("Users/{userId}/Items/{itemId}/Intros")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public Task<ActionResult<QueryResult<BaseItemDto>>> GetIntrosLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId) + => GetIntros(userId, itemId); + + /// <summary> + /// Marks an item as a favorite. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <response code="200">Item marked as favorite.</response> + /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns> + [HttpPost("UserFavoriteItems/{itemId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<UserItemDataDto> MarkFavoriteItem( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(userId.Value); + if (user is null) + { + return NotFound(); + } + + var item = itemId.IsEmpty() + ? _libraryManager.GetUserRootFolder() + : _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + return MarkFavorite(user, item, true); + } + + /// <summary> + /// Marks an item as a favorite. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <response code="200">Item marked as favorite.</response> + /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns> + [HttpPost("Users/{userId}/FavoriteItems/{itemId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult<UserItemDataDto> MarkFavoriteItemLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId) + => MarkFavoriteItem(userId, itemId); + + /// <summary> + /// Unmarks item as a favorite. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <response code="200">Item unmarked as favorite.</response> + /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns> + [HttpDelete("UserFavoriteItems/{itemId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<UserItemDataDto> UnmarkFavoriteItem( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(userId.Value); + if (user is null) + { + return NotFound(); + } + + var item = itemId.IsEmpty() + ? _libraryManager.GetUserRootFolder() + : _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + return MarkFavorite(user, item, false); + } + + /// <summary> + /// Unmarks item as a favorite. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <response code="200">Item unmarked as favorite.</response> + /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns> + [HttpDelete("Users/{userId}/FavoriteItems/{itemId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult<UserItemDataDto> UnmarkFavoriteItemLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId) + => UnmarkFavoriteItem(userId, itemId); + + /// <summary> + /// Deletes a user's saved personal rating for an item. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <response code="200">Personal rating removed.</response> + /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns> + [HttpDelete("UserItems/{itemId}/Rating")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<UserItemDataDto?> DeleteUserItemRating( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(userId.Value); + if (user is null) + { + return NotFound(); + } + + var item = itemId.IsEmpty() + ? _libraryManager.GetUserRootFolder() + : _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + return UpdateUserItemRatingInternal(user, item, null); + } + + /// <summary> + /// Deletes a user's saved personal rating for an item. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <response code="200">Personal rating removed.</response> + /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns> + [HttpDelete("Users/{userId}/Items/{itemId}/Rating")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult<UserItemDataDto?> DeleteUserItemRatingLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId) + => DeleteUserItemRating(userId, itemId); + + /// <summary> + /// Updates a user's rating for an item. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <param name="likes">Whether this <see cref="UpdateUserItemRating" /> is likes.</param> + /// <response code="200">Item rating updated.</response> + /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns> + [HttpPost("UserItems/{itemId}/Rating")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<UserItemDataDto?> UpdateUserItemRating( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId, + [FromQuery] bool? likes) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(userId.Value); + if (user is null) + { + return NotFound(); + } + + var item = itemId.IsEmpty() + ? _libraryManager.GetUserRootFolder() + : _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + return UpdateUserItemRatingInternal(user, item, likes); + } + + /// <summary> + /// Updates a user's rating for an item. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <param name="likes">Whether this <see cref="UpdateUserItemRating" /> is likes.</param> + /// <response code="200">Item rating updated.</response> + /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns> + [HttpPost("Users/{userId}/Items/{itemId}/Rating")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult<UserItemDataDto?> UpdateUserItemRatingLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId, + [FromQuery] bool? likes) + => UpdateUserItemRating(userId, itemId, likes); + + /// <summary> + /// Gets local trailers for an item. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <response code="200">An <see cref="OkResult"/> containing the item's local trailers.</response> + /// <returns>The items local trailers.</returns> + [HttpGet("Items/{itemId}/LocalTrailers")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<IEnumerable<BaseItemDto>> GetLocalTrailers( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(userId.Value); + if (user is null) + { + return NotFound(); + } + + var item = itemId.IsEmpty() + ? _libraryManager.GetUserRootFolder() + : _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + var dtoOptions = new DtoOptions(); + if (item is IHasTrailers hasTrailers) + { + var trailers = hasTrailers.LocalTrailers; + return Ok(_dtoService.GetBaseItemDtos(trailers, dtoOptions, user, item).AsEnumerable()); + } + + return Ok(item.GetExtras() + .Where(e => e.ExtraType == ExtraType.Trailer) + .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))); + } + + /// <summary> + /// Gets local trailers for an item. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <response code="200">An <see cref="OkResult"/> containing the item's local trailers.</response> + /// <returns>The items local trailers.</returns> + [HttpGet("Users/{userId}/Items/{itemId}/LocalTrailers")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult<IEnumerable<BaseItemDto>> GetLocalTrailersLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId) + => GetLocalTrailers(userId, itemId); + + /// <summary> + /// Gets special features for an item. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <response code="200">Special features returned.</response> + /// <returns>An <see cref="OkResult"/> containing the special features.</returns> + [HttpGet("Items/{itemId}/SpecialFeatures")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<IEnumerable<BaseItemDto>> GetSpecialFeatures( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(userId.Value); + if (user is null) + { + return NotFound(); + } + + var item = itemId.IsEmpty() + ? _libraryManager.GetUserRootFolder() + : _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + var dtoOptions = new DtoOptions(); + + return Ok(item + .GetExtras() + .Where(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contains(i.ExtraType.Value)) + .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))); + } + + /// <summary> + /// Gets special features for an item. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="itemId">Item id.</param> + /// <response code="200">Special features returned.</response> + /// <returns>An <see cref="OkResult"/> containing the special features.</returns> + [HttpGet("Users/{userId}/Items/{itemId}/SpecialFeatures")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult<IEnumerable<BaseItemDto>> GetSpecialFeaturesLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId) + => GetSpecialFeatures(userId, itemId); + + /// <summary> + /// Gets latest media. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param> + /// <param name="isPlayed">Filter by items that are played, or not.</param> + /// <param name="enableImages">Optional. include image information in output.</param> + /// <param name="imageTypeLimit">Optional. the max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="enableUserData">Optional. include user data.</param> + /// <param name="limit">Return item limit.</param> + /// <param name="groupItems">Whether or not to group items into a parent container.</param> + /// <response code="200">Latest media returned.</response> + /// <returns>An <see cref="OkResult"/> containing the latest media.</returns> + [HttpGet("Items/Latest")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<IEnumerable<BaseItemDto>> GetLatestMedia( + [FromQuery] Guid? userId, + [FromQuery] Guid? parentId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery] bool? isPlayed, + [FromQuery] bool? enableImages, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery] bool? enableUserData, + [FromQuery] int limit = 20, + [FromQuery] bool groupItems = true) + { + var requestUserId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(requestUserId); + if (user is null) + { + return NotFound(); + } + + if (!isPlayed.HasValue) + { + if (user.HidePlayedInLatest) + { + isPlayed = false; + } + } + + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + + var list = _userViewManager.GetLatestItems( + new LatestItemsQuery + { + GroupItems = groupItems, + IncludeItemTypes = includeItemTypes, + IsPlayed = isPlayed, + Limit = limit, + ParentId = parentId ?? Guid.Empty, + User = user, + }, + dtoOptions); + + var dtos = list.Select(i => + { + var item = i.Item2[0]; + var childCount = 0; + + if (i.Item1 is not null && (i.Item2.Count > 1 || i.Item1 is MusicAlbum || i.Item1 is Series )) + { + item = i.Item1; + childCount = i.Item2.Count; + } + + var dto = _dtoService.GetBaseItemDto(item, dtoOptions, user); + + dto.ChildCount = childCount; + + return dto; + }); + + return Ok(dtos); + } + + /// <summary> + /// Gets latest media. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param> + /// <param name="isPlayed">Filter by items that are played, or not.</param> + /// <param name="enableImages">Optional. include image information in output.</param> + /// <param name="imageTypeLimit">Optional. the max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="enableUserData">Optional. include user data.</param> + /// <param name="limit">Return item limit.</param> + /// <param name="groupItems">Whether or not to group items into a parent container.</param> + /// <response code="200">Latest media returned.</response> + /// <returns>An <see cref="OkResult"/> containing the latest media.</returns> + [HttpGet("Users/{userId}/Items/Latest")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult<IEnumerable<BaseItemDto>> GetLatestMediaLegacy( + [FromRoute, Required] Guid userId, + [FromQuery] Guid? parentId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery] bool? isPlayed, + [FromQuery] bool? enableImages, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery] bool? enableUserData, + [FromQuery] int limit = 20, + [FromQuery] bool groupItems = true) + => GetLatestMedia( + userId, + parentId, + fields, + includeItemTypes, + isPlayed, + enableImages, + imageTypeLimit, + enableImageTypes, + enableUserData, + limit, + groupItems); + + private async Task RefreshItemOnDemandIfNeeded(BaseItem item) + { + if (item is Person) + { + var hasMetadata = !string.IsNullOrWhiteSpace(item.Overview) && item.HasImage(ImageType.Primary); + var performFullRefresh = !hasMetadata && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= 3; + + if (!hasMetadata) + { + var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + MetadataRefreshMode = MetadataRefreshMode.FullRefresh, + ImageRefreshMode = MetadataRefreshMode.FullRefresh, + ForceSave = performFullRefresh + }; + + await item.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false); + } + } + } + + /// <summary> + /// Marks the favorite. + /// </summary> + /// <param name="user">The user.</param> + /// <param name="item">The item.</param> + /// <param name="isFavorite">if set to <c>true</c> [is favorite].</param> + private UserItemDataDto MarkFavorite(User user, BaseItem item, bool isFavorite) + { + // Get the user data for this item + var data = _userDataRepository.GetUserData(user, item); + + if (data is not null) + { + // Set favorite status + data.IsFavorite = isFavorite; + + _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None); + } + + return _userDataRepository.GetUserDataDto(item, user)!; + } + + /// <summary> + /// Updates the user item rating. + /// </summary> + /// <param name="user">The user.</param> + /// <param name="item">The item.</param> + /// <param name="likes">if set to <c>true</c> [likes].</param> + private UserItemDataDto? UpdateUserItemRatingInternal(User user, BaseItem item, bool? likes) + { + // Get the user data for this item + var data = _userDataRepository.GetUserData(user, item); + + if (data is not null) + { + data.Likes = likes; + + _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None); + } + + return _userDataRepository.GetUserDataDto(item, user); + } +} diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs new file mode 100644 index 00000000..ed4bba2b --- /dev/null +++ b/Jellyfin.Api/Controllers/UserViewsController.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Linq; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Api.Models.UserViewDtos; +using Jellyfin.Data.Enums; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Library; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// User views controller. +/// </summary> +[Route("")] +[Authorize] +public class UserViewsController : BaseJellyfinApiController +{ + private readonly IUserManager _userManager; + private readonly IUserViewManager _userViewManager; + private readonly IDtoService _dtoService; + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// Initializes a new instance of the <see cref="UserViewsController"/> class. + /// </summary> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="userViewManager">Instance of the <see cref="IUserViewManager"/> interface.</param> + /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + public UserViewsController( + IUserManager userManager, + IUserViewManager userViewManager, + IDtoService dtoService, + ILibraryManager libraryManager) + { + _userManager = userManager; + _userViewManager = userViewManager; + _dtoService = dtoService; + _libraryManager = libraryManager; + } + + /// <summary> + /// Get user views. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="includeExternalContent">Whether or not to include external views such as channels or live tv.</param> + /// <param name="presetViews">Preset views.</param> + /// <param name="includeHidden">Whether or not to include hidden content.</param> + /// <response code="200">User views returned.</response> + /// <returns>An <see cref="OkResult"/> containing the user views.</returns> + [HttpGet("UserViews")] + [ProducesResponseType(StatusCodes.Status200OK)] + public QueryResult<BaseItemDto> GetUserViews( + [FromQuery] Guid? userId, + [FromQuery] bool? includeExternalContent, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] CollectionType?[] presetViews, + [FromQuery] bool includeHidden = false) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(userId.Value) ?? throw new ResourceNotFoundException(); + + var query = new UserViewQuery { User = user, IncludeHidden = includeHidden }; + + if (includeExternalContent.HasValue) + { + query.IncludeExternalContent = includeExternalContent.Value; + } + + if (presetViews.Length != 0) + { + query.PresetViews = presetViews; + } + + var folders = _userViewManager.GetUserViews(query); + + var dtoOptions = new DtoOptions(); + dtoOptions.Fields = [..dtoOptions.Fields, ItemFields.PrimaryImageAspectRatio, ItemFields.DisplayPreferencesId]; + + var dtos = Array.ConvertAll(folders, i => _dtoService.GetBaseItemDto(i, dtoOptions, user)); + + return new QueryResult<BaseItemDto>(dtos); + } + + /// <summary> + /// Get user views. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="includeExternalContent">Whether or not to include external views such as channels or live tv.</param> + /// <param name="presetViews">Preset views.</param> + /// <param name="includeHidden">Whether or not to include hidden content.</param> + /// <response code="200">User views returned.</response> + /// <returns>An <see cref="OkResult"/> containing the user views.</returns> + [HttpGet("Users/{userId}/Views")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public QueryResult<BaseItemDto> GetUserViewsLegacy( + [FromRoute, Required] Guid userId, + [FromQuery] bool? includeExternalContent, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] CollectionType?[] presetViews, + [FromQuery] bool includeHidden = false) + => GetUserViews(userId, includeExternalContent, presetViews, includeHidden); + + /// <summary> + /// Get user view grouping options. + /// </summary> + /// <param name="userId">User id.</param> + /// <response code="200">User view grouping options returned.</response> + /// <response code="404">User not found.</response> + /// <returns> + /// An <see cref="OkResult"/> containing the user view grouping options + /// or a <see cref="NotFoundResult"/> if user not found. + /// </returns> + [HttpGet("UserViews/GroupingOptions")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<IEnumerable<SpecialViewOptionDto>> GetGroupingOptions([FromQuery] Guid? userId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(userId.Value); + if (user is null) + { + return NotFound(); + } + + return Ok(_libraryManager.GetUserRootFolder() + .GetChildren(user, true) + .OfType<Folder>() + .Where(UserView.IsEligibleForGrouping) + .Select(i => new SpecialViewOptionDto + { + Name = i.Name, + Id = i.Id.ToString("N", CultureInfo.InvariantCulture) + }) + .OrderBy(i => i.Name) + .AsEnumerable()); + } + + /// <summary> + /// Get user view grouping options. + /// </summary> + /// <param name="userId">User id.</param> + /// <response code="200">User view grouping options returned.</response> + /// <response code="404">User not found.</response> + /// <returns> + /// An <see cref="OkResult"/> containing the user view grouping options + /// or a <see cref="NotFoundResult"/> if user not found. + /// </returns> + [HttpGet("Users/{userId}/GroupingOptions")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult<IEnumerable<SpecialViewOptionDto>> GetGroupingOptionsLegacy( + [FromRoute, Required] Guid userId) + => GetGroupingOptions(userId); +} diff --git a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs new file mode 100644 index 00000000..b67c6fdb --- /dev/null +++ b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs @@ -0,0 +1,84 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Net.Mime; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Attributes; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Attachments controller. +/// </summary> +[Route("Videos")] +public class VideoAttachmentsController : BaseJellyfinApiController +{ + private readonly ILibraryManager _libraryManager; + private readonly IAttachmentExtractor _attachmentExtractor; + + /// <summary> + /// Initializes a new instance of the <see cref="VideoAttachmentsController"/> class. + /// </summary> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="attachmentExtractor">Instance of the <see cref="IAttachmentExtractor"/> interface.</param> + public VideoAttachmentsController( + ILibraryManager libraryManager, + IAttachmentExtractor attachmentExtractor) + { + _libraryManager = libraryManager; + _attachmentExtractor = attachmentExtractor; + } + + /// <summary> + /// Get video attachment. + /// </summary> + /// <param name="videoId">Video ID.</param> + /// <param name="mediaSourceId">Media Source ID.</param> + /// <param name="index">Attachment Index.</param> + /// <response code="200">Attachment retrieved.</response> + /// <response code="404">Video or attachment not found.</response> + /// <returns>An <see cref="FileStreamResult"/> containing the attachment stream on success, or a <see cref="NotFoundResult"/> if the attachment could not be found.</returns> + [HttpGet("{videoId}/{mediaSourceId}/Attachments/{index}")] + [ProducesFile(MediaTypeNames.Application.Octet)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> GetAttachment( + [FromRoute, Required] Guid videoId, + [FromRoute, Required] string mediaSourceId, + [FromRoute, Required] int index) + { + try + { + var item = _libraryManager.GetItemById<BaseItem>(videoId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + var (attachment, stream) = await _attachmentExtractor.GetAttachment( + item, + mediaSourceId, + index, + CancellationToken.None) + .ConfigureAwait(false); + + var contentType = string.IsNullOrWhiteSpace(attachment.MimeType) + ? MediaTypeNames.Application.Octet + : attachment.MimeType; + + return new FileStreamResult(stream, contentType); + } + catch (ResourceNotFoundException e) + { + return NotFound(e.Message); + } + } +} diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs new file mode 100644 index 00000000..ccf8e906 --- /dev/null +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -0,0 +1,658 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Attributes; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Extensions; +using MediaBrowser.Common.Api; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// The videos controller. +/// </summary> +public class VideosController : BaseJellyfinApiController +{ + private readonly ILibraryManager _libraryManager; + private readonly IUserManager _userManager; + private readonly IDtoService _dtoService; + private readonly IMediaSourceManager _mediaSourceManager; + private readonly IServerConfigurationManager _serverConfigurationManager; + private readonly IMediaEncoder _mediaEncoder; + private readonly ITranscodeManager _transcodeManager; + private readonly IHttpClientFactory _httpClientFactory; + private readonly EncodingHelper _encodingHelper; + + private readonly TranscodingJobType _transcodingJobType = TranscodingJobType.Progressive; + + /// <summary> + /// Initializes a new instance of the <see cref="VideosController"/> class. + /// </summary> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> + /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> + /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param> + /// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param> + /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param> + public VideosController( + ILibraryManager libraryManager, + IUserManager userManager, + IDtoService dtoService, + IMediaSourceManager mediaSourceManager, + IServerConfigurationManager serverConfigurationManager, + IMediaEncoder mediaEncoder, + ITranscodeManager transcodeManager, + IHttpClientFactory httpClientFactory, + EncodingHelper encodingHelper) + { + _libraryManager = libraryManager; + _userManager = userManager; + _dtoService = dtoService; + _mediaSourceManager = mediaSourceManager; + _serverConfigurationManager = serverConfigurationManager; + _mediaEncoder = mediaEncoder; + _transcodeManager = transcodeManager; + _httpClientFactory = httpClientFactory; + _encodingHelper = encodingHelper; + } + + /// <summary> + /// Gets additional parts for a video. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <response code="200">Additional parts returned.</response> + /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the parts.</returns> + [HttpGet("{itemId}/AdditionalParts")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetAdditionalPart([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + + var item = itemId.IsEmpty() + ? (userId.IsNullOrEmpty() + ? _libraryManager.RootFolder + : _libraryManager.GetUserRootFolder()) + : _libraryManager.GetItemById<BaseItem>(itemId, user); + if (item is null) + { + return NotFound(); + } + + var dtoOptions = new DtoOptions(); + + BaseItemDto[] items; + if (item is Video video) + { + items = video.GetAdditionalParts() + .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, video)) + .ToArray(); + } + else + { + items = Array.Empty<BaseItemDto>(); + } + + var result = new QueryResult<BaseItemDto>(items); + return result; + } + + /// <summary> + /// Removes alternate video sources. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <response code="204">Alternate sources deleted.</response> + /// <response code="404">Video not found.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success, or a <see cref="NotFoundResult"/> if the video doesn't exist.</returns> + [HttpDelete("{itemId}/AlternateSources")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult> DeleteAlternateSources([FromRoute, Required] Guid itemId) + { + var item = _libraryManager.GetItemById<Video>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + if (item.LinkedAlternateVersions.Length == 0) + { + item = _libraryManager.GetItemById<Video>(Guid.Parse(item.PrimaryVersionId)); + } + + if (item is null) + { + return NotFound(); + } + + foreach (var link in item.GetLinkedAlternateVersions()) + { + link.SetPrimaryVersionId(null); + link.LinkedAlternateVersions = Array.Empty<LinkedChild>(); + + await link.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + } + + item.LinkedAlternateVersions = Array.Empty<LinkedChild>(); + item.SetPrimaryVersionId(null); + await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + + return NoContent(); + } + + /// <summary> + /// Merges videos into a single record. + /// </summary> + /// <param name="ids">Item id list. This allows multiple, comma delimited.</param> + /// <response code="204">Videos merged.</response> + /// <response code="400">Supply at least 2 video ids.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success, or a <see cref="BadRequestResult"/> if less than two ids were supplied.</returns> + [HttpPost("MergeVersions")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task<ActionResult> MergeVersions([FromQuery, Required, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids) + { + var userId = User.GetUserId(); + var items = ids + .Select(i => _libraryManager.GetItemById<BaseItem>(i, userId)) + .OfType<Video>() + .OrderBy(i => i.Id) + .ToList(); + + if (items.Count < 2) + { + return BadRequest("Please supply at least two videos to merge."); + } + + var primaryVersion = items.FirstOrDefault(i => i.MediaSourceCount > 1 && string.IsNullOrEmpty(i.PrimaryVersionId)); + if (primaryVersion is null) + { + primaryVersion = items + .OrderBy(i => + { + if (i.Video3DFormat.HasValue || i.VideoType != VideoType.VideoFile) + { + return 1; + } + + return 0; + }) + .ThenByDescending(i => i.GetDefaultVideoStream()?.Width ?? 0) + .First(); + } + + var alternateVersionsOfPrimary = primaryVersion.LinkedAlternateVersions.ToList(); + + foreach (var item in items.Where(i => !i.Id.Equals(primaryVersion.Id))) + { + item.SetPrimaryVersionId(primaryVersion.Id.ToString("N", CultureInfo.InvariantCulture)); + + await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + + if (!alternateVersionsOfPrimary.Any(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase))) + { + alternateVersionsOfPrimary.Add(new LinkedChild + { + Path = item.Path, + ItemId = item.Id + }); + } + + foreach (var linkedItem in item.LinkedAlternateVersions) + { + if (!alternateVersionsOfPrimary.Any(i => string.Equals(i.Path, linkedItem.Path, StringComparison.OrdinalIgnoreCase))) + { + alternateVersionsOfPrimary.Add(linkedItem); + } + } + + if (item.LinkedAlternateVersions.Length > 0) + { + item.LinkedAlternateVersions = Array.Empty<LinkedChild>(); + await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + } + } + + primaryVersion.LinkedAlternateVersions = alternateVersionsOfPrimary.ToArray(); + await primaryVersion.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + return NoContent(); + } + + /// <summary> + /// Gets a video stream. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param> + /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param> + /// <param name="params">The streaming parameters.</param> + /// <param name="tag">The tag.</param> + /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param> + /// <param name="playSessionId">The play session id.</param> + /// <param name="segmentContainer">The segment container.</param> + /// <param name="segmentLength">The segment length.</param> + /// <param name="minSegments">The minimum number of segments.</param> + /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> + /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension.</param> + /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> + /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> + /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> + /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param> + /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param> + /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param> + /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param> + /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param> + /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param> + /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param> + /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param> + /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param> + /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param> + /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param> + /// <param name="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param> + /// <param name="maxHeight">Optional. The maximum vertical resolution of the encoded video.</param> + /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param> + /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param> + /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param> + /// <param name="maxRefFrames">Optional.</param> + /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param> + /// <param name="requireAvc">Optional. Whether to require avc.</param> + /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param> + /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param> + /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param> + /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> + /// <param name="liveStreamId">The live stream id.</param> + /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension.</param> + /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> + /// <param name="transcodeReasons">Optional. The transcoding reason.</param> + /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> + /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param> + /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param> + /// <param name="streamOptions">Optional. The streaming options.</param> + /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param> + /// <response code="200">Video stream returned.</response> + /// <returns>A <see cref="FileResult"/> containing the audio file.</returns> + [HttpGet("{itemId}/stream")] + [HttpHead("{itemId}/stream", Name = "HeadVideoStream")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesVideoFile] + public async Task<ActionResult> GetVideoStream( + [FromRoute, Required] Guid itemId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? container, + [FromQuery] bool? @static, + [FromQuery] string? @params, + [FromQuery] string? tag, + [FromQuery, ParameterObsolete] string? deviceProfileId, + [FromQuery] string? playSessionId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer, + [FromQuery] int? segmentLength, + [FromQuery] int? minSegments, + [FromQuery] string? mediaSourceId, + [FromQuery] string? deviceId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec, + [FromQuery] bool? enableAutoStreamCopy, + [FromQuery] bool? allowVideoStreamCopy, + [FromQuery] bool? allowAudioStreamCopy, + [FromQuery] int? audioSampleRate, + [FromQuery] int? maxAudioBitDepth, + [FromQuery] int? audioBitRate, + [FromQuery] int? audioChannels, + [FromQuery] int? maxAudioChannels, + [FromQuery] string? profile, + [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level, + [FromQuery] float? framerate, + [FromQuery] float? maxFramerate, + [FromQuery] bool? copyTimestamps, + [FromQuery] long? startTimeTicks, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] int? videoBitRate, + [FromQuery] int? subtitleStreamIndex, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, + [FromQuery] int? maxRefFrames, + [FromQuery] int? maxVideoBitDepth, + [FromQuery] bool? requireAvc, + [FromQuery] bool? deInterlace, + [FromQuery] bool? requireNonAnamorphic, + [FromQuery] int? transcodingMaxAudioChannels, + [FromQuery] int? cpuCoreLimit, + [FromQuery] string? liveStreamId, + [FromQuery] bool? enableMpegtsM2TsMode, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec, + [FromQuery] string? transcodeReasons, + [FromQuery] int? audioStreamIndex, + [FromQuery] int? videoStreamIndex, + [FromQuery] EncodingContext? context, + [FromQuery] Dictionary<string, string> streamOptions, + [FromQuery] bool enableAudioVbrEncoding = true) + { + var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head; + // CTS lifecycle is managed internally. + var cancellationTokenSource = new CancellationTokenSource(); + var streamingRequest = new VideoRequestDto + { + Id = itemId, + Container = container, + Static = @static ?? false, + Params = @params, + Tag = tag, + PlaySessionId = playSessionId, + SegmentContainer = segmentContainer, + SegmentLength = segmentLength, + MinSegments = minSegments, + MediaSourceId = mediaSourceId, + DeviceId = deviceId, + AudioCodec = audioCodec, + EnableAutoStreamCopy = enableAutoStreamCopy ?? true, + AllowAudioStreamCopy = allowAudioStreamCopy ?? true, + AllowVideoStreamCopy = allowVideoStreamCopy ?? true, + AudioSampleRate = audioSampleRate, + MaxAudioChannels = maxAudioChannels, + AudioBitRate = audioBitRate, + MaxAudioBitDepth = maxAudioBitDepth, + AudioChannels = audioChannels, + Profile = profile, + Level = level, + Framerate = framerate, + MaxFramerate = maxFramerate, + CopyTimestamps = copyTimestamps ?? false, + StartTimeTicks = startTimeTicks, + Width = width, + Height = height, + MaxWidth = maxWidth, + MaxHeight = maxHeight, + VideoBitRate = videoBitRate, + SubtitleStreamIndex = subtitleStreamIndex, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, + MaxRefFrames = maxRefFrames, + MaxVideoBitDepth = maxVideoBitDepth, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, + TranscodingMaxAudioChannels = transcodingMaxAudioChannels, + CpuCoreLimit = cpuCoreLimit, + LiveStreamId = liveStreamId, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, + VideoCodec = videoCodec, + SubtitleCodec = subtitleCodec, + TranscodeReasons = transcodeReasons, + AudioStreamIndex = audioStreamIndex, + VideoStreamIndex = videoStreamIndex, + Context = context ?? EncodingContext.Streaming, + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding + }; + + var state = await StreamingHelpers.GetStreamingState( + streamingRequest, + HttpContext, + _mediaSourceManager, + _userManager, + _libraryManager, + _serverConfigurationManager, + _mediaEncoder, + _encodingHelper, + _transcodeManager, + _transcodingJobType, + cancellationTokenSource.Token) + .ConfigureAwait(false); + + if (@static.HasValue && @static.Value && state.DirectStreamProvider is not null) + { + var liveStreamInfo = _mediaSourceManager.GetLiveStreamInfo(streamingRequest.LiveStreamId); + if (liveStreamInfo is null) + { + return NotFound(); + } + + var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream()); + // TODO (moved from MediaBrowser.Api): Don't hardcode contentType + return File(liveStream, MimeTypes.GetMimeType("file.ts")); + } + + // Static remote stream + if (@static.HasValue && @static.Value && state.InputProtocol == MediaProtocol.Http) + { + var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); + return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, httpClient, HttpContext).ConfigureAwait(false); + } + + if (@static.HasValue && @static.Value && state.InputProtocol != MediaProtocol.File) + { + return BadRequest($"Input protocol {state.InputProtocol} cannot be streamed statically"); + } + + // Static stream + if (@static.HasValue && @static.Value && !(state.MediaSource.VideoType == VideoType.BluRay || state.MediaSource.VideoType == VideoType.Dvd)) + { + var contentType = state.GetMimeType("." + state.OutputContainer, false) ?? state.GetMimeType(state.MediaPath); + + if (state.MediaSource.IsInfiniteStream) + { + var liveStream = new ProgressiveFileStream(state.MediaPath, null, _transcodeManager); + return File(liveStream, contentType); + } + + return FileStreamResponseHelpers.GetStaticFileResult( + state.MediaPath, + contentType); + } + + // Need to start ffmpeg (because media can't be returned directly) + var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); + var ffmpegCommandLineArguments = _encodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, EncoderPreset.superfast); + return await FileStreamResponseHelpers.GetTranscodedFile( + state, + isHeadRequest, + HttpContext, + _transcodeManager, + ffmpegCommandLineArguments, + _transcodingJobType, + cancellationTokenSource).ConfigureAwait(false); + } + + /// <summary> + /// Gets a video stream. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param> + /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param> + /// <param name="params">The streaming parameters.</param> + /// <param name="tag">The tag.</param> + /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param> + /// <param name="playSessionId">The play session id.</param> + /// <param name="segmentContainer">The segment container.</param> + /// <param name="segmentLength">The segment length.</param> + /// <param name="minSegments">The minimum number of segments.</param> + /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> + /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension.</param> + /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> + /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> + /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> + /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param> + /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param> + /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param> + /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param> + /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param> + /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param> + /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param> + /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param> + /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param> + /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param> + /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param> + /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param> + /// <param name="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param> + /// <param name="maxHeight">Optional. The maximum vertical resolution of the encoded video.</param> + /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param> + /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param> + /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param> + /// <param name="maxRefFrames">Optional.</param> + /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param> + /// <param name="requireAvc">Optional. Whether to require avc.</param> + /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param> + /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param> + /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param> + /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> + /// <param name="liveStreamId">The live stream id.</param> + /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension.</param> + /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> + /// <param name="transcodeReasons">Optional. The transcoding reason.</param> + /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> + /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param> + /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param> + /// <param name="streamOptions">Optional. The streaming options.</param> + /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param> + /// <response code="200">Video stream returned.</response> + /// <returns>A <see cref="FileResult"/> containing the audio file.</returns> + [HttpGet("{itemId}/stream.{container}")] + [HttpHead("{itemId}/stream.{container}", Name = "HeadVideoStreamByContainer")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesVideoFile] + public Task<ActionResult> GetVideoStreamByContainer( + [FromRoute, Required] Guid itemId, + [FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container, + [FromQuery] bool? @static, + [FromQuery] string? @params, + [FromQuery] string? tag, + [FromQuery] string? deviceProfileId, + [FromQuery] string? playSessionId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer, + [FromQuery] int? segmentLength, + [FromQuery] int? minSegments, + [FromQuery] string? mediaSourceId, + [FromQuery] string? deviceId, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec, + [FromQuery] bool? enableAutoStreamCopy, + [FromQuery] bool? allowVideoStreamCopy, + [FromQuery] bool? allowAudioStreamCopy, + [FromQuery] int? audioSampleRate, + [FromQuery] int? maxAudioBitDepth, + [FromQuery] int? audioBitRate, + [FromQuery] int? audioChannels, + [FromQuery] int? maxAudioChannels, + [FromQuery] string? profile, + [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level, + [FromQuery] float? framerate, + [FromQuery] float? maxFramerate, + [FromQuery] bool? copyTimestamps, + [FromQuery] long? startTimeTicks, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] int? videoBitRate, + [FromQuery] int? subtitleStreamIndex, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, + [FromQuery] int? maxRefFrames, + [FromQuery] int? maxVideoBitDepth, + [FromQuery] bool? requireAvc, + [FromQuery] bool? deInterlace, + [FromQuery] bool? requireNonAnamorphic, + [FromQuery] int? transcodingMaxAudioChannels, + [FromQuery] int? cpuCoreLimit, + [FromQuery] string? liveStreamId, + [FromQuery] bool? enableMpegtsM2TsMode, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec, + [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec, + [FromQuery] string? transcodeReasons, + [FromQuery] int? audioStreamIndex, + [FromQuery] int? videoStreamIndex, + [FromQuery] EncodingContext? context, + [FromQuery] Dictionary<string, string> streamOptions, + [FromQuery] bool enableAudioVbrEncoding = true) + { + return GetVideoStream( + itemId, + container, + @static, + @params, + tag, + deviceProfileId, + playSessionId, + segmentContainer, + segmentLength, + minSegments, + mediaSourceId, + deviceId, + audioCodec, + enableAutoStreamCopy, + allowVideoStreamCopy, + allowAudioStreamCopy, + audioSampleRate, + maxAudioBitDepth, + audioBitRate, + audioChannels, + maxAudioChannels, + profile, + level, + framerate, + maxFramerate, + copyTimestamps, + startTimeTicks, + width, + height, + maxWidth, + maxHeight, + videoBitRate, + subtitleStreamIndex, + subtitleMethod, + maxRefFrames, + maxVideoBitDepth, + requireAvc, + deInterlace, + requireNonAnamorphic, + transcodingMaxAudioChannels, + cpuCoreLimit, + liveStreamId, + enableMpegtsM2TsMode, + videoCodec, + subtitleCodec, + transcodeReasons, + audioStreamIndex, + videoStreamIndex, + context, + streamOptions, + enableAudioVbrEncoding); + } +} diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs new file mode 100644 index 00000000..685334a9 --- /dev/null +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Years controller. +/// </summary> +[Authorize] +public class YearsController : BaseJellyfinApiController +{ + private readonly ILibraryManager _libraryManager; + private readonly IUserManager _userManager; + private readonly IDtoService _dtoService; + + /// <summary> + /// Initializes a new instance of the <see cref="YearsController"/> class. + /// </summary> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> + public YearsController( + ILibraryManager libraryManager, + IUserManager userManager, + IDtoService dtoService) + { + _libraryManager = libraryManager; + _userManager = userManager; + _dtoService = dtoService; + } + + /// <summary> + /// Get years. + /// </summary> + /// <param name="startIndex">Skips over a given number of items within the results. Use for paging.</param> + /// <param name="limit">Optional. The maximum number of records to return.</param> + /// <param name="sortOrder">Sort Order - Ascending,Descending.</param> + /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param> + /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> + /// <param name="excludeItemTypes">Optional. If specified, results will be excluded based on item type. This allows multiple, comma delimited.</param> + /// <param name="includeItemTypes">Optional. If specified, results will be included based on item type. This allows multiple, comma delimited.</param> + /// <param name="mediaTypes">Optional. Filter by MediaType. Allows multiple, comma delimited.</param> + /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param> + /// <param name="enableUserData">Optional. Include user data.</param> + /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> + /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> + /// <param name="userId">User Id.</param> + /// <param name="recursive">Search recursively.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> + /// <response code="200">Year query returned.</response> + /// <returns> A <see cref="QueryResult{BaseItemDto}"/> containing the year result.</returns> + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<QueryResult<BaseItemDto>> GetYears( + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, + [FromQuery] Guid? parentId, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery] Guid? userId, + [FromQuery] bool recursive = true, + [FromQuery] bool? enableImages = true) + { + userId = RequestHelpers.GetUserId(User, userId); + var dtoOptions = new DtoOptions { Fields = fields } + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + + User? user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId); + + var query = new InternalItemsQuery(user) + { + ExcludeItemTypes = excludeItemTypes, + IncludeItemTypes = includeItemTypes, + MediaTypes = mediaTypes, + DtoOptions = dtoOptions + }; + + bool Filter(BaseItem i) => FilterItem(i, excludeItemTypes, includeItemTypes, mediaTypes); + + IReadOnlyList<BaseItem> items; + int totalCount = -1; + if (parentItem.IsFolder) + { + var folder = (Folder)parentItem; + + if (userId.IsNullOrEmpty()) + { + items = recursive ? folder.GetRecursiveChildren(Filter) : folder.Children.Where(Filter).ToArray(); + } + else + { + items = recursive ? folder.GetRecursiveChildren(user, query, out totalCount) : folder.GetChildren(user, true).Where(Filter).ToArray(); + } + } + else + { + items = new[] { parentItem }.Where(Filter).ToList(); + } + + var extractedItems = GetAllItems(items); + + var filteredItems = _libraryManager.Sort(extractedItems, user, RequestHelpers.GetOrderBy(sortBy, sortOrder)); + + var ibnItemsArray = filteredItems.ToList(); + + IEnumerable<BaseItem> ibnItems = ibnItemsArray; + + if (startIndex.HasValue || limit.HasValue) + { + if (startIndex.HasValue) + { + ibnItems = ibnItems.Skip(startIndex.Value); + } + + if (limit.HasValue) + { + ibnItems = ibnItems.Take(limit.Value); + } + } + + var tuples = ibnItems.Select(i => new Tuple<BaseItem, List<BaseItem>>(i, new List<BaseItem>())); + + var dtos = tuples.Select(i => _dtoService.GetItemByNameDto(i.Item1, dtoOptions, i.Item2, user)); + + var result = new QueryResult<BaseItemDto>( + startIndex, + totalCount == -1 ? ibnItemsArray.Count : totalCount, + dtos.Where(i => i is not null).ToArray()); + return result; + } + + /// <summary> + /// Gets a year. + /// </summary> + /// <param name="year">The year.</param> + /// <param name="userId">Optional. Filter by user id, and attach user data.</param> + /// <response code="200">Year returned.</response> + /// <response code="404">Year not found.</response> + /// <returns> + /// An <see cref="OkResult"/> containing the year, + /// or a <see cref="NotFoundResult"/> if year not found. + /// </returns> + [HttpGet("{year}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<BaseItemDto> GetYear([FromRoute, Required] int year, [FromQuery] Guid? userId) + { + userId = RequestHelpers.GetUserId(User, userId); + var item = _libraryManager.GetYear(year); + if (item is null) + { + return NotFound(); + } + + var dtoOptions = new DtoOptions(); + + if (!userId.IsNullOrEmpty()) + { + var user = _userManager.GetUserById(userId.Value); + return _dtoService.GetBaseItemDto(item, dtoOptions, user); + } + + return _dtoService.GetBaseItemDto(item, dtoOptions); + } + + private bool FilterItem(BaseItem f, IReadOnlyCollection<BaseItemKind> excludeItemTypes, IReadOnlyCollection<BaseItemKind> includeItemTypes, IReadOnlyCollection<MediaType> mediaTypes) + { + var baseItemKind = f.GetBaseItemKind(); + // Exclude item types + if (excludeItemTypes.Count > 0 && excludeItemTypes.Contains(baseItemKind)) + { + return false; + } + + // Include item types + if (includeItemTypes.Count > 0 && !includeItemTypes.Contains(baseItemKind)) + { + return false; + } + + // Include MediaTypes + if (mediaTypes.Count > 0 && !mediaTypes.Contains(f.MediaType)) + { + return false; + } + + return true; + } + + private IEnumerable<BaseItem> GetAllItems(IEnumerable<BaseItem> items) + { + return items + .Select(i => i.ProductionYear ?? 0) + .Where(i => i > 0) + .Distinct() + .Select(_libraryManager.GetYear); + } +} diff --git a/Jellyfin.Api/Extensions/ClaimsPrincipalExtensions.cs b/Jellyfin.Api/Extensions/ClaimsPrincipalExtensions.cs new file mode 100644 index 00000000..60890556 --- /dev/null +++ b/Jellyfin.Api/Extensions/ClaimsPrincipalExtensions.cs @@ -0,0 +1,80 @@ +using System; +using System.Linq; +using System.Security.Claims; +using Jellyfin.Api.Constants; + +namespace Jellyfin.Api.Extensions; + +/// <summary> +/// Extensions for <see cref="ClaimsPrincipal"/>. +/// </summary> +public static class ClaimsPrincipalExtensions +{ + /// <summary> + /// Get user id from claims. + /// </summary> + /// <param name="user">Current claims principal.</param> + /// <returns>User id.</returns> + public static Guid GetUserId(this ClaimsPrincipal user) + { + var value = GetClaimValue(user, InternalClaimTypes.UserId); + return string.IsNullOrEmpty(value) + ? default + : Guid.Parse(value); + } + + /// <summary> + /// Get device id from claims. + /// </summary> + /// <param name="user">Current claims principal.</param> + /// <returns>Device id.</returns> + public static string? GetDeviceId(this ClaimsPrincipal user) + => GetClaimValue(user, InternalClaimTypes.DeviceId); + + /// <summary> + /// Get device from claims. + /// </summary> + /// <param name="user">Current claims principal.</param> + /// <returns>Device.</returns> + public static string? GetDevice(this ClaimsPrincipal user) + => GetClaimValue(user, InternalClaimTypes.Device); + + /// <summary> + /// Get client from claims. + /// </summary> + /// <param name="user">Current claims principal.</param> + /// <returns>Client.</returns> + public static string? GetClient(this ClaimsPrincipal user) + => GetClaimValue(user, InternalClaimTypes.Client); + + /// <summary> + /// Get version from claims. + /// </summary> + /// <param name="user">Current claims principal.</param> + /// <returns>Version.</returns> + public static string? GetVersion(this ClaimsPrincipal user) + => GetClaimValue(user, InternalClaimTypes.Version); + + /// <summary> + /// Get token from claims. + /// </summary> + /// <param name="user">Current claims principal.</param> + /// <returns>Token.</returns> + public static string? GetToken(this ClaimsPrincipal user) + => GetClaimValue(user, InternalClaimTypes.Token); + + /// <summary> + /// Gets a flag specifying whether the request is using an api key. + /// </summary> + /// <param name="user">Current claims principal.</param> + /// <returns>The flag specifying whether the request is using an api key.</returns> + public static bool GetIsApiKey(this ClaimsPrincipal user) + { + var claimValue = GetClaimValue(user, InternalClaimTypes.IsApiKey); + return bool.TryParse(claimValue, out var parsedClaimValue) + && parsedClaimValue; + } + + private static string? GetClaimValue(in ClaimsPrincipal user, string name) + => user.Claims.FirstOrDefault(claim => claim.Type.Equals(name, StringComparison.OrdinalIgnoreCase))?.Value; +} diff --git a/Jellyfin.Api/Extensions/DtoExtensions.cs b/Jellyfin.Api/Extensions/DtoExtensions.cs new file mode 100644 index 00000000..9c24be82 --- /dev/null +++ b/Jellyfin.Api/Extensions/DtoExtensions.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Model.Entities; + +namespace Jellyfin.Api.Extensions; + +/// <summary> +/// Dto Extensions. +/// </summary> +public static class DtoExtensions +{ + /// <summary> + /// Add additional DtoOptions. + /// </summary> + /// <remarks> + /// Converted from IHasDtoOptions. + /// Legacy order: 3. + /// </remarks> + /// <param name="dtoOptions">DtoOptions object.</param> + /// <param name="enableImages">Enable images.</param> + /// <param name="enableUserData">Enable user data.</param> + /// <param name="imageTypeLimit">Image type limit.</param> + /// <param name="enableImageTypes">Enable image types.</param> + /// <returns>Modified DtoOptions object.</returns> + internal static DtoOptions AddAdditionalDtoOptions( + this DtoOptions dtoOptions, + bool? enableImages, + bool? enableUserData, + int? imageTypeLimit, + IReadOnlyList<ImageType> enableImageTypes) + { + dtoOptions.EnableImages = enableImages ?? true; + + if (imageTypeLimit.HasValue) + { + dtoOptions.ImageTypeLimit = imageTypeLimit.Value; + } + + if (enableUserData.HasValue) + { + dtoOptions.EnableUserData = enableUserData.Value; + } + + if (enableImageTypes.Count != 0) + { + dtoOptions.ImageTypes = enableImageTypes; + } + + return dtoOptions; + } +} diff --git a/Jellyfin.Api/Formatters/CamelCaseJsonProfileFormatter.cs b/Jellyfin.Api/Formatters/CamelCaseJsonProfileFormatter.cs new file mode 100644 index 00000000..96b29b1c --- /dev/null +++ b/Jellyfin.Api/Formatters/CamelCaseJsonProfileFormatter.cs @@ -0,0 +1,20 @@ +using Jellyfin.Extensions.Json; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Net.Http.Headers; + +namespace Jellyfin.Api.Formatters; + +/// <summary> +/// Camel Case Json Profile Formatter. +/// </summary> +public class CamelCaseJsonProfileFormatter : SystemTextJsonOutputFormatter +{ + /// <summary> + /// Initializes a new instance of the <see cref="CamelCaseJsonProfileFormatter"/> class. + /// </summary> + public CamelCaseJsonProfileFormatter() : base(JsonDefaults.CamelCaseOptions) + { + SupportedMediaTypes.Clear(); + SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(JsonDefaults.CamelCaseMediaType)); + } +} diff --git a/Jellyfin.Api/Formatters/CssOutputFormatter.cs b/Jellyfin.Api/Formatters/CssOutputFormatter.cs new file mode 100644 index 00000000..9ad1c863 --- /dev/null +++ b/Jellyfin.Api/Formatters/CssOutputFormatter.cs @@ -0,0 +1,19 @@ +using System.Net.Mime; +using Microsoft.AspNetCore.Mvc.Formatters; + +namespace Jellyfin.Api.Formatters; + +/// <summary> +/// Css output formatter. +/// </summary> +public sealed class CssOutputFormatter : StringOutputFormatter +{ + /// <summary> + /// Initializes a new instance of the <see cref="CssOutputFormatter"/> class. + /// </summary> + public CssOutputFormatter() + { + SupportedMediaTypes.Clear(); + SupportedMediaTypes.Add(MediaTypeNames.Text.Css); + } +} diff --git a/Jellyfin.Api/Formatters/PascalCaseJsonProfileFormatter.cs b/Jellyfin.Api/Formatters/PascalCaseJsonProfileFormatter.cs new file mode 100644 index 00000000..b5b57527 --- /dev/null +++ b/Jellyfin.Api/Formatters/PascalCaseJsonProfileFormatter.cs @@ -0,0 +1,23 @@ +using System.Net.Mime; +using Jellyfin.Extensions.Json; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Net.Http.Headers; + +namespace Jellyfin.Api.Formatters; + +/// <summary> +/// Pascal Case Json Profile Formatter. +/// </summary> +public class PascalCaseJsonProfileFormatter : SystemTextJsonOutputFormatter +{ + /// <summary> + /// Initializes a new instance of the <see cref="PascalCaseJsonProfileFormatter"/> class. + /// </summary> + public PascalCaseJsonProfileFormatter() : base(JsonDefaults.PascalCaseOptions) + { + SupportedMediaTypes.Clear(); + // Add application/json for default formatter + SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json)); + SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(JsonDefaults.PascalCaseMediaType)); + } +} diff --git a/Jellyfin.Api/Formatters/XmlOutputFormatter.cs b/Jellyfin.Api/Formatters/XmlOutputFormatter.cs new file mode 100644 index 00000000..46256c09 --- /dev/null +++ b/Jellyfin.Api/Formatters/XmlOutputFormatter.cs @@ -0,0 +1,42 @@ +using System; +using System.Net.Mime; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Formatters; + +namespace Jellyfin.Api.Formatters; + +/// <summary> +/// Xml output formatter. +/// </summary> +public sealed class XmlOutputFormatter : TextOutputFormatter +{ + /// <summary> + /// Initializes a new instance of the <see cref="XmlOutputFormatter"/> class. + /// </summary> + public XmlOutputFormatter() + { + SupportedMediaTypes.Clear(); + SupportedMediaTypes.Add(MediaTypeNames.Text.Xml); + + SupportedEncodings.Add(Encoding.UTF8); + SupportedEncodings.Add(Encoding.Unicode); + } + + /// <inheritdoc /> + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(selectedEncoding); + + var valueAsString = context.Object?.ToString(); + if (string.IsNullOrEmpty(valueAsString)) + { + return; + } + + var response = context.HttpContext.Response; + await response.WriteAsync(valueAsString, selectedEncoding).ConfigureAwait(false); + } +} diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs new file mode 100644 index 00000000..1c9d6e44 --- /dev/null +++ b/Jellyfin.Api/Helpers/AudioHelper.cs @@ -0,0 +1,157 @@ +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; +using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Helpers; + +/// <summary> +/// Audio helper. +/// </summary> +public class AudioHelper +{ + private readonly IUserManager _userManager; + private readonly ILibraryManager _libraryManager; + private readonly IMediaSourceManager _mediaSourceManager; + private readonly IServerConfigurationManager _serverConfigurationManager; + private readonly IMediaEncoder _mediaEncoder; + private readonly ITranscodeManager _transcodeManager; + private readonly IHttpClientFactory _httpClientFactory; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly EncodingHelper _encodingHelper; + + /// <summary> + /// Initializes a new instance of the <see cref="AudioHelper"/> class. + /// </summary> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> + /// <param name="transcodeManager">Instance of <see cref="ITranscodeManager"/> interface.</param> + /// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param> + /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param> + /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param> + public AudioHelper( + IUserManager userManager, + ILibraryManager libraryManager, + IMediaSourceManager mediaSourceManager, + IServerConfigurationManager serverConfigurationManager, + IMediaEncoder mediaEncoder, + ITranscodeManager transcodeManager, + IHttpClientFactory httpClientFactory, + IHttpContextAccessor httpContextAccessor, + EncodingHelper encodingHelper) + { + _userManager = userManager; + _libraryManager = libraryManager; + _mediaSourceManager = mediaSourceManager; + _serverConfigurationManager = serverConfigurationManager; + _mediaEncoder = mediaEncoder; + _transcodeManager = transcodeManager; + _httpClientFactory = httpClientFactory; + _httpContextAccessor = httpContextAccessor; + _encodingHelper = encodingHelper; + } + + /// <summary> + /// Get audio stream. + /// </summary> + /// <param name="transcodingJobType">Transcoding job type.</param> + /// <param name="streamingRequest">Streaming controller.Request dto.</param> + /// <returns>A <see cref="Task"/> containing the resulting <see cref="ActionResult"/>.</returns> + public async Task<ActionResult> GetAudioStream( + TranscodingJobType transcodingJobType, + StreamingRequestDto streamingRequest) + { + if (_httpContextAccessor.HttpContext is null) + { + throw new ResourceNotFoundException(nameof(_httpContextAccessor.HttpContext)); + } + + bool isHeadRequest = _httpContextAccessor.HttpContext.Request.Method == System.Net.WebRequestMethods.Http.Head; + + // CTS lifecycle is managed internally. + var cancellationTokenSource = new CancellationTokenSource(); + + using var state = await StreamingHelpers.GetStreamingState( + streamingRequest, + _httpContextAccessor.HttpContext, + _mediaSourceManager, + _userManager, + _libraryManager, + _serverConfigurationManager, + _mediaEncoder, + _encodingHelper, + _transcodeManager, + transcodingJobType, + cancellationTokenSource.Token) + .ConfigureAwait(false); + + if (streamingRequest.Static && state.DirectStreamProvider is not null) + { + var liveStreamInfo = _mediaSourceManager.GetLiveStreamInfo(streamingRequest.LiveStreamId); + if (liveStreamInfo is null) + { + throw new FileNotFoundException(); + } + + var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream()); + // TODO (moved from MediaBrowser.Api): Don't hardcode contentType + return new FileStreamResult(liveStream, MimeTypes.GetMimeType("file.ts")); + } + + // Static remote stream + if (streamingRequest.Static && state.InputProtocol == MediaProtocol.Http) + { + var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); + return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, httpClient, _httpContextAccessor.HttpContext).ConfigureAwait(false); + } + + if (streamingRequest.Static && state.InputProtocol != MediaProtocol.File) + { + return new BadRequestObjectResult($"Input protocol {state.InputProtocol} cannot be streamed statically"); + } + + var outputPath = state.OutputFilePath; + + // Static stream + if (streamingRequest.Static) + { + var contentType = state.GetMimeType("." + state.OutputContainer, false) ?? state.GetMimeType(state.MediaPath); + + if (state.MediaSource.IsInfiniteStream) + { + var stream = new ProgressiveFileStream(state.MediaPath, null, _transcodeManager); + return new FileStreamResult(stream, contentType); + } + + return FileStreamResponseHelpers.GetStaticFileResult( + state.MediaPath, + contentType); + } + + // Need to start ffmpeg (because media can't be returned directly) + var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); + var ffmpegCommandLineArguments = _encodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, outputPath); + return await FileStreamResponseHelpers.GetTranscodedFile( + state, + isHeadRequest, + _httpContextAccessor.HttpContext, + _transcodeManager, + ffmpegCommandLineArguments, + transcodingJobType, + cancellationTokenSource).ConfigureAwait(false); + } +} diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs new file mode 100644 index 00000000..44e1c6d5 --- /dev/null +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -0,0 +1,920 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Security.Claims; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Extensions; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Extensions; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; +using MediaBrowser.Controller.Trickplay; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace Jellyfin.Api.Helpers; + +/// <summary> +/// Dynamic hls helper. +/// </summary> +public class DynamicHlsHelper +{ + private readonly ILibraryManager _libraryManager; + private readonly IUserManager _userManager; + private readonly IMediaSourceManager _mediaSourceManager; + private readonly IServerConfigurationManager _serverConfigurationManager; + private readonly IMediaEncoder _mediaEncoder; + private readonly ITranscodeManager _transcodeManager; + private readonly INetworkManager _networkManager; + private readonly ILogger<DynamicHlsHelper> _logger; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly EncodingHelper _encodingHelper; + private readonly ITrickplayManager _trickplayManager; + + /// <summary> + /// Initializes a new instance of the <see cref="DynamicHlsHelper"/> class. + /// </summary> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> + /// <param name="transcodeManager">Instance of <see cref="ITranscodeManager"/>.</param> + /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param> + /// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsHelper}"/> interface.</param> + /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param> + /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param> + /// <param name="trickplayManager">Instance of <see cref="ITrickplayManager"/>.</param> + public DynamicHlsHelper( + ILibraryManager libraryManager, + IUserManager userManager, + IMediaSourceManager mediaSourceManager, + IServerConfigurationManager serverConfigurationManager, + IMediaEncoder mediaEncoder, + ITranscodeManager transcodeManager, + INetworkManager networkManager, + ILogger<DynamicHlsHelper> logger, + IHttpContextAccessor httpContextAccessor, + EncodingHelper encodingHelper, + ITrickplayManager trickplayManager) + { + _libraryManager = libraryManager; + _userManager = userManager; + _mediaSourceManager = mediaSourceManager; + _serverConfigurationManager = serverConfigurationManager; + _mediaEncoder = mediaEncoder; + _transcodeManager = transcodeManager; + _networkManager = networkManager; + _logger = logger; + _httpContextAccessor = httpContextAccessor; + _encodingHelper = encodingHelper; + _trickplayManager = trickplayManager; + } + + /// <summary> + /// Get master hls playlist. + /// </summary> + /// <param name="transcodingJobType">Transcoding job type.</param> + /// <param name="streamingRequest">Streaming request dto.</param> + /// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param> + /// <returns>A <see cref="Task"/> containing the resulting <see cref="ActionResult"/>.</returns> + public async Task<ActionResult> GetMasterHlsPlaylist( + TranscodingJobType transcodingJobType, + StreamingRequestDto streamingRequest, + bool enableAdaptiveBitrateStreaming) + { + var isHeadRequest = _httpContextAccessor.HttpContext?.Request.Method == WebRequestMethods.Http.Head; + // CTS lifecycle is managed internally. + var cancellationTokenSource = new CancellationTokenSource(); + return await GetMasterPlaylistInternal( + streamingRequest, + isHeadRequest, + enableAdaptiveBitrateStreaming, + transcodingJobType, + cancellationTokenSource).ConfigureAwait(false); + } + + private async Task<ActionResult> GetMasterPlaylistInternal( + StreamingRequestDto streamingRequest, + bool isHeadRequest, + bool enableAdaptiveBitrateStreaming, + TranscodingJobType transcodingJobType, + CancellationTokenSource cancellationTokenSource) + { + if (_httpContextAccessor.HttpContext is null) + { + throw new ResourceNotFoundException(nameof(_httpContextAccessor.HttpContext)); + } + + using var state = await StreamingHelpers.GetStreamingState( + streamingRequest, + _httpContextAccessor.HttpContext, + _mediaSourceManager, + _userManager, + _libraryManager, + _serverConfigurationManager, + _mediaEncoder, + _encodingHelper, + _transcodeManager, + transcodingJobType, + cancellationTokenSource.Token) + .ConfigureAwait(false); + + _httpContextAccessor.HttpContext.Response.Headers.Append(HeaderNames.Expires, "0"); + if (isHeadRequest) + { + return new FileContentResult(Array.Empty<byte>(), MimeTypes.GetMimeType("playlist.m3u8")); + } + + var totalBitrate = (state.OutputAudioBitrate ?? 0) + (state.OutputVideoBitrate ?? 0); + + var builder = new StringBuilder(); + + builder.AppendLine("#EXTM3U"); + + var isLiveStream = state.IsSegmentedLiveStream; + + var queryString = _httpContextAccessor.HttpContext.Request.QueryString.ToString(); + + // from universal audio service, need to override the AudioCodec when the actual request differs from original query + if (!string.Equals(state.OutputAudioCodec, _httpContextAccessor.HttpContext.Request.Query["AudioCodec"].ToString(), StringComparison.OrdinalIgnoreCase)) + { + var newQuery = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(queryString); + newQuery["AudioCodec"] = state.OutputAudioCodec; + queryString = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(string.Empty, newQuery); + } + + // from universal audio service + if (!string.IsNullOrWhiteSpace(state.Request.SegmentContainer) + && !queryString.Contains("SegmentContainer", StringComparison.OrdinalIgnoreCase)) + { + queryString += "&SegmentContainer=" + state.Request.SegmentContainer; + } + + // from universal audio service + if (!string.IsNullOrWhiteSpace(state.Request.TranscodeReasons) + && !queryString.Contains("TranscodeReasons=", StringComparison.OrdinalIgnoreCase)) + { + queryString += "&TranscodeReasons=" + state.Request.TranscodeReasons; + } + + // Video rotation metadata is only supported in fMP4 remuxing + if (state.VideoStream is not null + && state.VideoRequest is not null + && (state.VideoStream?.Rotation ?? 0) != 0 + && EncodingHelper.IsCopyCodec(state.OutputVideoCodec) + && !string.IsNullOrWhiteSpace(state.Request.SegmentContainer) + && !string.Equals(state.Request.SegmentContainer, "mp4", StringComparison.OrdinalIgnoreCase)) + { + queryString += "&AllowVideoStreamCopy=false"; + } + + // Main stream + var baseUrl = isLiveStream ? "live.m3u8" : "main.m3u8"; + var playlistUrl = baseUrl + queryString; + var playlistQuery = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(queryString); + + var subtitleStreams = state.MediaSource + .MediaStreams + .Where(i => i.IsTextSubtitleStream) + .ToList(); + + var subtitleGroup = subtitleStreams.Count > 0 && (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Hls || state.VideoRequest!.EnableSubtitlesInManifest) + ? "subs" + : null; + + // If we're burning in subtitles then don't add additional subs to the manifest + if (state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) + { + subtitleGroup = null; + } + + if (!string.IsNullOrWhiteSpace(subtitleGroup)) + { + AddSubtitles(state, subtitleStreams, builder, _httpContextAccessor.HttpContext.User); + } + + var basicPlaylist = AppendPlaylist(builder, state, playlistUrl, totalBitrate, subtitleGroup); + + if (state.VideoStream is not null && state.VideoRequest is not null) + { + var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); + + // Provide AV1 and HEVC SDR entrances for backward compatibility. + foreach (var sdrVideoCodec in new[] { "av1", "hevc" }) + { + var isAv1EncodingAllowed = encodingOptions.AllowAv1Encoding + && string.Equals(sdrVideoCodec, "av1", StringComparison.OrdinalIgnoreCase) + && string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase); + var isHevcEncodingAllowed = encodingOptions.AllowHevcEncoding + && string.Equals(sdrVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase) + && string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase); + var isEncodingAllowed = isAv1EncodingAllowed || isHevcEncodingAllowed; + + if (isEncodingAllowed + && EncodingHelper.IsCopyCodec(state.OutputVideoCodec) + && state.VideoStream.VideoRange == VideoRange.HDR) + { + // Force AV1 and HEVC Main Profile and disable video stream copy. + state.OutputVideoCodec = sdrVideoCodec; + + var sdrPlaylistQuery = playlistQuery; + sdrPlaylistQuery["VideoCodec"] = sdrVideoCodec; + sdrPlaylistQuery[sdrVideoCodec + "-profile"] = "main"; + sdrPlaylistQuery["AllowVideoStreamCopy"] = "false"; + + var sdrVideoUrl = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(baseUrl, sdrPlaylistQuery); + + // HACK: Use the same bitrate so that the client can choose by other attributes, such as color range. + AppendPlaylist(builder, state, sdrVideoUrl, totalBitrate, subtitleGroup); + + // Restore the video codec + state.OutputVideoCodec = "copy"; + } + } + + // Provide H.264 SDR entrance for backward compatibility. + if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec) + && state.VideoStream.VideoRange == VideoRange.HDR) + { + // Force H.264 and disable video stream copy. + state.OutputVideoCodec = "h264"; + + var sdrPlaylistQuery = playlistQuery; + sdrPlaylistQuery["VideoCodec"] = "h264"; + sdrPlaylistQuery["AllowVideoStreamCopy"] = "false"; + + var sdrVideoUrl = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(baseUrl, sdrPlaylistQuery); + + // HACK: Use the same bitrate so that the client can choose by other attributes, such as color range. + AppendPlaylist(builder, state, sdrVideoUrl, totalBitrate, subtitleGroup); + + // Restore the video codec + state.OutputVideoCodec = "copy"; + } + + // Provide Level 5.0 entrance for backward compatibility. + // e.g. Apple A10 chips refuse the master playlist containing SDR HEVC Main Level 5.1 video, + // but in fact it is capable of playing videos up to Level 6.1. + if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec) + && state.VideoStream.Level.HasValue + && state.VideoStream.Level > 150 + && state.VideoStream.VideoRange == VideoRange.SDR + && string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)) + { + var playlistCodecsField = new StringBuilder(); + AppendPlaylistCodecsField(playlistCodecsField, state); + + // Force the video level to 5.0. + var originalLevel = state.VideoStream.Level; + state.VideoStream.Level = 150; + var newPlaylistCodecsField = new StringBuilder(); + AppendPlaylistCodecsField(newPlaylistCodecsField, state); + + // Restore the video level. + state.VideoStream.Level = originalLevel; + var newPlaylist = ReplacePlaylistCodecsField(basicPlaylist, playlistCodecsField, newPlaylistCodecsField); + builder.Append(newPlaylist); + } + } + + if (EnableAdaptiveBitrateStreaming(state, isLiveStream, enableAdaptiveBitrateStreaming, _httpContextAccessor.HttpContext.GetNormalizedRemoteIP())) + { + var requestedVideoBitrate = state.VideoRequest?.VideoBitRate ?? 0; + + // By default, vary by just 200k + var variation = GetBitrateVariation(totalBitrate); + + var newBitrate = totalBitrate - variation; + var variantQuery = playlistQuery; + variantQuery["VideoBitrate"] = (requestedVideoBitrate - variation).ToString(CultureInfo.InvariantCulture); + var variantUrl = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(baseUrl, variantQuery); + AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup); + + variation *= 2; + newBitrate = totalBitrate - variation; + variantQuery["VideoBitrate"] = (requestedVideoBitrate - variation).ToString(CultureInfo.InvariantCulture); + variantUrl = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(baseUrl, variantQuery); + AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup); + } + + if (!isLiveStream && (state.VideoRequest?.EnableTrickplay ?? false)) + { + var sourceId = Guid.Parse(state.Request.MediaSourceId); + var trickplayResolutions = await _trickplayManager.GetTrickplayResolutions(sourceId).ConfigureAwait(false); + AddTrickplay(state, trickplayResolutions, builder, _httpContextAccessor.HttpContext.User); + } + + return new FileContentResult(Encoding.UTF8.GetBytes(builder.ToString()), MimeTypes.GetMimeType("playlist.m3u8")); + } + + private StringBuilder AppendPlaylist(StringBuilder builder, StreamState state, string url, int bitrate, string? subtitleGroup) + { + var playlistBuilder = new StringBuilder(); + playlistBuilder.Append("#EXT-X-STREAM-INF:BANDWIDTH=") + .Append(bitrate.ToString(CultureInfo.InvariantCulture)) + .Append(",AVERAGE-BANDWIDTH=") + .Append(bitrate.ToString(CultureInfo.InvariantCulture)); + + AppendPlaylistVideoRangeField(playlistBuilder, state); + + AppendPlaylistCodecsField(playlistBuilder, state); + + AppendPlaylistSupplementalCodecsField(playlistBuilder, state); + + AppendPlaylistResolutionField(playlistBuilder, state); + + AppendPlaylistFramerateField(playlistBuilder, state); + + if (!string.IsNullOrWhiteSpace(subtitleGroup)) + { + playlistBuilder.Append(",SUBTITLES=\"") + .Append(subtitleGroup) + .Append('"'); + } + + playlistBuilder.Append(Environment.NewLine); + playlistBuilder.AppendLine(url); + builder.Append(playlistBuilder); + + return playlistBuilder; + } + + /// <summary> + /// Appends a VIDEO-RANGE field containing the range of the output video stream. + /// </summary> + /// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/> + /// <param name="builder">StringBuilder to append the field to.</param> + /// <param name="state">StreamState of the current stream.</param> + private void AppendPlaylistVideoRangeField(StringBuilder builder, StreamState state) + { + if (state.VideoStream is not null && state.VideoStream.VideoRange != VideoRange.Unknown) + { + var videoRange = state.VideoStream.VideoRange; + var videoRangeType = state.VideoStream.VideoRangeType; + if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) + { + if (videoRange == VideoRange.SDR) + { + builder.Append(",VIDEO-RANGE=SDR"); + } + + if (videoRange == VideoRange.HDR) + { + switch (videoRangeType) + { + case VideoRangeType.HLG: + case VideoRangeType.DOVIWithHLG: + builder.Append(",VIDEO-RANGE=HLG"); + break; + default: + builder.Append(",VIDEO-RANGE=PQ"); + break; + } + } + } + else + { + // Currently we only encode to SDR. + builder.Append(",VIDEO-RANGE=SDR"); + } + } + } + + /// <summary> + /// Appends a CODECS field containing formatted strings of + /// the active streams output video and audio codecs. + /// </summary> + /// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/> + /// <seealso cref="GetPlaylistVideoCodecs(StreamState, string, int)"/> + /// <seealso cref="GetPlaylistAudioCodecs(StreamState)"/> + /// <param name="builder">StringBuilder to append the field to.</param> + /// <param name="state">StreamState of the current stream.</param> + private void AppendPlaylistCodecsField(StringBuilder builder, StreamState state) + { + // Video + string videoCodecs = string.Empty; + int? videoCodecLevel = GetOutputVideoCodecLevel(state); + if (!string.IsNullOrEmpty(state.ActualOutputVideoCodec) && videoCodecLevel.HasValue) + { + videoCodecs = GetPlaylistVideoCodecs(state, state.ActualOutputVideoCodec, videoCodecLevel.Value); + } + + // Audio + string audioCodecs = string.Empty; + if (!string.IsNullOrEmpty(state.ActualOutputAudioCodec)) + { + audioCodecs = GetPlaylistAudioCodecs(state); + } + + StringBuilder codecs = new StringBuilder(); + + codecs.Append(videoCodecs); + + if (!string.IsNullOrEmpty(videoCodecs) && !string.IsNullOrEmpty(audioCodecs)) + { + codecs.Append(','); + } + + codecs.Append(audioCodecs); + + if (codecs.Length > 1) + { + builder.Append(",CODECS=\"") + .Append(codecs) + .Append('"'); + } + } + + /// <summary> + /// Appends a SUPPLEMENTAL-CODECS field containing formatted strings of + /// the active streams output Dolby Vision Videos. + /// </summary> + /// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/> + /// <seealso cref="GetPlaylistVideoCodecs(StreamState, string, int)"/> + /// <param name="builder">StringBuilder to append the field to.</param> + /// <param name="state">StreamState of the current stream.</param> + private void AppendPlaylistSupplementalCodecsField(StringBuilder builder, StreamState state) + { + // HDR dynamic metadata currently cannot exist when transcoding + if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) + { + return; + } + + if (EncodingHelper.IsDovi(state.VideoStream) && !_encodingHelper.IsDoviRemoved(state)) + { + AppendDvString(); + } + else if (EncodingHelper.IsHdr10Plus(state.VideoStream) && !_encodingHelper.IsHdr10PlusRemoved(state)) + { + AppendHdr10PlusString(); + } + + return; + + void AppendDvString() + { + var dvProfile = state.VideoStream.DvProfile; + var dvLevel = state.VideoStream.DvLevel; + var dvRangeString = state.VideoStream.VideoRangeType switch + { + VideoRangeType.DOVIWithHDR10 => "db1p", + VideoRangeType.DOVIWithHLG => "db4h", + VideoRangeType.DOVIWithHDR10Plus => "db1p", // The HDR10+ metadata would be removed if Dovi metadata is not removed + _ => string.Empty // Don't label Dovi with EL and SDR due to compatability issues, ignore invalid configurations + }; + + if (dvProfile is null || dvLevel is null || string.IsNullOrEmpty(dvRangeString)) + { + return; + } + + var dvFourCc = string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase) ? "dav1" : "dvh1"; + builder.Append(",SUPPLEMENTAL-CODECS=\"") + .Append(dvFourCc) + .Append('.') + .Append(dvProfile.Value.ToString("D2", CultureInfo.InvariantCulture)) + .Append('.') + .Append(dvLevel.Value.ToString("D2", CultureInfo.InvariantCulture)) + .Append('/') + .Append(dvRangeString) + .Append('"'); + } + + void AppendHdr10PlusString() + { + var videoCodecLevel = GetOutputVideoCodecLevel(state); + if (string.IsNullOrEmpty(state.ActualOutputVideoCodec) || videoCodecLevel is null) + { + return; + } + + var videoCodecString = GetPlaylistVideoCodecs(state, state.ActualOutputVideoCodec, videoCodecLevel.Value); + builder.Append(",SUPPLEMENTAL-CODECS=\"") + .Append(videoCodecString) + .Append('/') + .Append("cdm4") + .Append('"'); + } + } + + /// <summary> + /// Appends a RESOLUTION field containing the resolution of the output stream. + /// </summary> + /// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/> + /// <param name="builder">StringBuilder to append the field to.</param> + /// <param name="state">StreamState of the current stream.</param> + private void AppendPlaylistResolutionField(StringBuilder builder, StreamState state) + { + if (state.OutputWidth.HasValue && state.OutputHeight.HasValue) + { + builder.Append(",RESOLUTION=") + .Append(state.OutputWidth.GetValueOrDefault()) + .Append('x') + .Append(state.OutputHeight.GetValueOrDefault()); + } + } + + /// <summary> + /// Appends a FRAME-RATE field containing the framerate of the output stream. + /// </summary> + /// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/> + /// <param name="builder">StringBuilder to append the field to.</param> + /// <param name="state">StreamState of the current stream.</param> + private void AppendPlaylistFramerateField(StringBuilder builder, StreamState state) + { + double? framerate = null; + if (state.TargetFramerate.HasValue) + { + framerate = Math.Round(state.TargetFramerate.GetValueOrDefault(), 3); + } + else if (state.VideoStream?.RealFrameRate is not null) + { + framerate = Math.Round(state.VideoStream.RealFrameRate.GetValueOrDefault(), 3); + } + + if (framerate.HasValue) + { + builder.Append(",FRAME-RATE=") + .Append(framerate.Value.ToString(CultureInfo.InvariantCulture)); + } + } + + private bool EnableAdaptiveBitrateStreaming(StreamState state, bool isLiveStream, bool enableAdaptiveBitrateStreaming, IPAddress ipAddress) + { + // Within the local network this will likely do more harm than good. + if (_networkManager.IsInLocalNetwork(ipAddress)) + { + return false; + } + + if (!enableAdaptiveBitrateStreaming) + { + return false; + } + + if (isLiveStream || string.IsNullOrWhiteSpace(state.MediaPath)) + { + // Opening live streams is so slow it's not even worth it + return false; + } + + if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) + { + return false; + } + + if (EncodingHelper.IsCopyCodec(state.OutputAudioCodec)) + { + return false; + } + + if (!state.IsOutputVideo) + { + return false; + } + + return state.VideoRequest?.VideoBitRate.HasValue ?? false; + } + + private void AddSubtitles(StreamState state, IEnumerable<MediaStream> subtitles, StringBuilder builder, ClaimsPrincipal user) + { + if (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Drop) + { + return; + } + + var selectedIndex = state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Hls ? (int?)null : state.SubtitleStream.Index; + const string Format = "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"{0}\",DEFAULT={1},FORCED={2},AUTOSELECT=YES,URI=\"{3}\",LANGUAGE=\"{4}\""; + + foreach (var stream in subtitles) + { + var name = stream.DisplayTitle; + + var isDefault = selectedIndex.HasValue && selectedIndex.Value == stream.Index; + var isForced = stream.IsForced; + + var url = string.Format( + CultureInfo.InvariantCulture, + "{0}/Subtitles/{1}/subtitles.m3u8?SegmentLength={2}&ApiKey={3}", + state.Request.MediaSourceId, + stream.Index.ToString(CultureInfo.InvariantCulture), + 30.ToString(CultureInfo.InvariantCulture), + user.GetToken()); + + var line = string.Format( + CultureInfo.InvariantCulture, + Format, + name, + isDefault ? "YES" : "NO", + isForced ? "YES" : "NO", + url, + stream.Language ?? "Unknown"); + + builder.AppendLine(line); + } + } + + /// <summary> + /// Appends EXT-X-IMAGE-STREAM-INF playlists for each available trickplay resolution. + /// </summary> + /// <param name="state">StreamState of the current stream.</param> + /// <param name="trickplayResolutions">Dictionary of widths to corresponding tiles info.</param> + /// <param name="builder">StringBuilder to append the field to.</param> + /// <param name="user">Http user context.</param> + private void AddTrickplay(StreamState state, Dictionary<int, TrickplayInfo> trickplayResolutions, StringBuilder builder, ClaimsPrincipal user) + { + const string playlistFormat = "#EXT-X-IMAGE-STREAM-INF:BANDWIDTH={0},RESOLUTION={1}x{2},CODECS=\"jpeg\",URI=\"{3}\""; + + foreach (var resolution in trickplayResolutions) + { + var width = resolution.Key; + var trickplayInfo = resolution.Value; + + var url = string.Format( + CultureInfo.InvariantCulture, + "Trickplay/{0}/tiles.m3u8?MediaSourceId={1}&ApiKey={2}", + width.ToString(CultureInfo.InvariantCulture), + state.Request.MediaSourceId, + user.GetToken()); + + builder.AppendFormat( + CultureInfo.InvariantCulture, + playlistFormat, + trickplayInfo.Bandwidth.ToString(CultureInfo.InvariantCulture), + trickplayInfo.Width.ToString(CultureInfo.InvariantCulture), + trickplayInfo.Height.ToString(CultureInfo.InvariantCulture), + url); + + builder.AppendLine(); + } + } + + /// <summary> + /// Get the H.26X level of the output video stream. + /// </summary> + /// <param name="state">StreamState of the current stream.</param> + /// <returns>H.26X level of the output video stream.</returns> + private int? GetOutputVideoCodecLevel(StreamState state) + { + string levelString = string.Empty; + if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec) + && state.VideoStream is not null + && state.VideoStream.Level.HasValue) + { + levelString = state.VideoStream.Level.Value.ToString(CultureInfo.InvariantCulture); + } + else + { + if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase)) + { + levelString = state.GetRequestedLevel(state.ActualOutputVideoCodec) ?? "41"; + levelString = EncodingHelper.NormalizeTranscodingLevel(state, levelString); + } + + if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)) + { + levelString = state.GetRequestedLevel("h265") ?? state.GetRequestedLevel("hevc") ?? "120"; + levelString = EncodingHelper.NormalizeTranscodingLevel(state, levelString); + } + + if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase)) + { + levelString = state.GetRequestedLevel("av1") ?? "19"; + levelString = EncodingHelper.NormalizeTranscodingLevel(state, levelString); + } + } + + if (int.TryParse(levelString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedLevel)) + { + return parsedLevel; + } + + return null; + } + + /// <summary> + /// Get the profile of the output video stream. + /// </summary> + /// <param name="state">StreamState of the current stream.</param> + /// <param name="codec">Video codec.</param> + /// <returns>Profile of the output video stream.</returns> + private string GetOutputVideoCodecProfile(StreamState state, string codec) + { + string profileString = string.Empty; + if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec) + && !string.IsNullOrEmpty(state.VideoStream.Profile)) + { + profileString = state.VideoStream.Profile; + } + else if (!string.IsNullOrEmpty(codec)) + { + profileString = state.GetRequestedProfiles(codec).FirstOrDefault() ?? string.Empty; + if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase)) + { + profileString ??= "high"; + } + + if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase)) + { + profileString ??= "main"; + } + } + + return profileString; + } + + /// <summary> + /// Gets a formatted string of the output audio codec, for use in the CODECS field. + /// </summary> + /// <seealso cref="AppendPlaylistCodecsField(StringBuilder, StreamState)"/> + /// <seealso cref="GetPlaylistVideoCodecs(StreamState, string, int)"/> + /// <param name="state">StreamState of the current stream.</param> + /// <returns>Formatted audio codec string.</returns> + private string GetPlaylistAudioCodecs(StreamState state) + { + if (string.Equals(state.ActualOutputAudioCodec, "aac", StringComparison.OrdinalIgnoreCase)) + { + string? profile = EncodingHelper.IsCopyCodec(state.OutputAudioCodec) + ? state.AudioStream?.Profile : state.GetRequestedProfiles("aac").FirstOrDefault(); + + return HlsCodecStringHelpers.GetAACString(profile); + } + + if (string.Equals(state.ActualOutputAudioCodec, "mp3", StringComparison.OrdinalIgnoreCase)) + { + return HlsCodecStringHelpers.GetMP3String(); + } + + if (string.Equals(state.ActualOutputAudioCodec, "ac3", StringComparison.OrdinalIgnoreCase)) + { + return HlsCodecStringHelpers.GetAC3String(); + } + + if (string.Equals(state.ActualOutputAudioCodec, "eac3", StringComparison.OrdinalIgnoreCase)) + { + return HlsCodecStringHelpers.GetEAC3String(); + } + + if (string.Equals(state.ActualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase)) + { + return HlsCodecStringHelpers.GetFLACString(); + } + + if (string.Equals(state.ActualOutputAudioCodec, "alac", StringComparison.OrdinalIgnoreCase)) + { + return HlsCodecStringHelpers.GetALACString(); + } + + if (string.Equals(state.ActualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)) + { + return HlsCodecStringHelpers.GetOPUSString(); + } + + if (string.Equals(state.ActualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase)) + { + return HlsCodecStringHelpers.GetTRUEHDString(); + } + + if (string.Equals(state.ActualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase)) + { + // lavc only support encoding DTS core profile + string? profile = EncodingHelper.IsCopyCodec(state.OutputAudioCodec) ? state.AudioStream?.Profile : "DTS"; + + return HlsCodecStringHelpers.GetDTSString(profile); + } + + return string.Empty; + } + + /// <summary> + /// Gets a formatted string of the output video codec, for use in the CODECS field. + /// </summary> + /// <seealso cref="AppendPlaylistCodecsField(StringBuilder, StreamState)"/> + /// <seealso cref="GetPlaylistAudioCodecs(StreamState)"/> + /// <param name="state">StreamState of the current stream.</param> + /// <param name="codec">Video codec.</param> + /// <param name="level">Video level.</param> + /// <returns>Formatted video codec string.</returns> + private string GetPlaylistVideoCodecs(StreamState state, string codec, int level) + { + if (level == 0) + { + // This is 0 when there's no requested level in the device profile + // and the source is not encoded in H.26X or AV1 + _logger.LogError("Got invalid level when building CODECS field for HLS master playlist"); + return string.Empty; + } + + if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)) + { + string profile = GetOutputVideoCodecProfile(state, "h264"); + return HlsCodecStringHelpers.GetH264String(profile, level); + } + + if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)) + { + string profile = GetOutputVideoCodecProfile(state, "hevc"); + return HlsCodecStringHelpers.GetH265String(profile, level); + } + + if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase)) + { + string profile = GetOutputVideoCodecProfile(state, "av1"); + + // Currently we only transcode to 8 bits AV1 + int bitDepth = 8; + if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec) + && state.VideoStream is not null + && state.VideoStream.BitDepth.HasValue) + { + bitDepth = state.VideoStream.BitDepth.Value; + } + + return HlsCodecStringHelpers.GetAv1String(profile, level, false, bitDepth); + } + + // VP9 HLS is for video remuxing only, everything is probed from the original video + if (string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)) + { + var width = state.VideoStream.Width ?? 0; + var height = state.VideoStream.Height ?? 0; + var framerate = state.VideoStream.ReferenceFrameRate ?? 30; + var bitDepth = state.VideoStream.BitDepth ?? 8; + return HlsCodecStringHelpers.GetVp9String( + width, + height, + state.VideoStream.PixelFormat, + framerate, + bitDepth); + } + + return string.Empty; + } + + private int GetBitrateVariation(int bitrate) + { + // By default, vary by just 50k + var variation = 50000; + + if (bitrate >= 10000000) + { + variation = 2000000; + } + else if (bitrate >= 5000000) + { + variation = 1500000; + } + else if (bitrate >= 3000000) + { + variation = 1000000; + } + else if (bitrate >= 2000000) + { + variation = 500000; + } + else if (bitrate >= 1000000) + { + variation = 300000; + } + else if (bitrate >= 600000) + { + variation = 200000; + } + else if (bitrate >= 400000) + { + variation = 100000; + } + + return variation; + } + + private string ReplacePlaylistCodecsField(StringBuilder playlist, StringBuilder oldValue, StringBuilder newValue) + { + var oldPlaylist = playlist.ToString(); + return oldPlaylist.Replace( + oldValue.ToString(), + newValue.ToString(), + StringComparison.Ordinal); + } +} diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs new file mode 100644 index 00000000..4034a808 --- /dev/null +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -0,0 +1,169 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Net.Mime; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Extensions; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Net.Http.Headers; + +namespace Jellyfin.Api.Helpers; + +/// <summary> +/// The stream response helpers. +/// </summary> +public static class FileStreamResponseHelpers +{ + /// <summary> + /// Returns a static file from a remote source. + /// </summary> + /// <param name="state">The current <see cref="StreamState"/>.</param> + /// <param name="httpClient">The <see cref="HttpClient"/> making the remote request.</param> + /// <param name="httpContext">The current http context.</param> + /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param> + /// <returns>A <see cref="Task{ActionResult}"/> containing the API response.</returns> + public static async Task<ActionResult> GetStaticRemoteStreamResult( + StreamState state, + HttpClient httpClient, + HttpContext httpContext, + CancellationToken cancellationToken = default) + { + using var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri(state.MediaPath)); + + // Forward User-Agent if provided + if (state.RemoteHttpHeaders.TryGetValue(HeaderNames.UserAgent, out var useragent)) + { + // Clear default and add specific one if exists, otherwise HttpClient default might be used + requestMessage.Headers.UserAgent.Clear(); + requestMessage.Headers.TryAddWithoutValidation(HeaderNames.UserAgent, useragent); + } + + // Forward Range header if present in the client request + if (httpContext.Request.Headers.TryGetValue(HeaderNames.Range, out var rangeValue)) + { + var rangeString = rangeValue.ToString(); + if (!string.IsNullOrEmpty(rangeString)) + { + requestMessage.Headers.Range = System.Net.Http.Headers.RangeHeaderValue.Parse(rangeString); + } + } + + // Send the request to the upstream server + // Use ResponseHeadersRead to avoid downloading the whole content immediately + var response = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + + // Check if the upstream server supports range requests and acted upon our Range header + bool upstreamSupportsRange = response.StatusCode == System.Net.HttpStatusCode.PartialContent; + string acceptRangesValue = "none"; + if (response.Headers.TryGetValues(HeaderNames.AcceptRanges, out var acceptRangesHeaders)) + { + // Prefer upstream server's Accept-Ranges header if available + acceptRangesValue = string.Join(", ", acceptRangesHeaders); + upstreamSupportsRange |= acceptRangesValue.Contains("bytes", StringComparison.OrdinalIgnoreCase); + } + else if (upstreamSupportsRange) // If we got 206 but no Accept-Ranges header, assume bytes + { + acceptRangesValue = "bytes"; + } + + // Set Accept-Ranges header for the client based on upstream support + httpContext.Response.Headers[HeaderNames.AcceptRanges] = acceptRangesValue; + + // Set Content-Range header if upstream provided it (implies partial content) + if (response.Content.Headers.ContentRange is not null) + { + httpContext.Response.Headers[HeaderNames.ContentRange] = response.Content.Headers.ContentRange.ToString(); + } + + // Set Content-Length header. For partial content, this is the length of the partial segment. + if (response.Content.Headers.ContentLength.HasValue) + { + httpContext.Response.ContentLength = response.Content.Headers.ContentLength.Value; + } + + // Set Content-Type header + var contentType = response.Content.Headers.ContentType?.ToString() ?? MediaTypeNames.Application.Octet; // Use a more generic default + + // Set the status code for the client response (e.g., 200 OK or 206 Partial Content) + httpContext.Response.StatusCode = (int)response.StatusCode; + + // Return the stream from the upstream server + // IMPORTANT: Do not dispose the response stream here, FileStreamResult will handle it. + return new FileStreamResult(await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false), contentType); + } + + /// <summary> + /// Returns a static file from the server. + /// </summary> + /// <param name="path">The path to the file.</param> + /// <param name="contentType">The content type of the file.</param> + /// <returns>An <see cref="ActionResult"/> the file.</returns> + public static ActionResult GetStaticFileResult( + string path, + string contentType) + { + return new PhysicalFileResult(path, contentType) { EnableRangeProcessing = true }; + } + + /// <summary> + /// Returns a transcoded file from the server. + /// </summary> + /// <param name="state">The current <see cref="StreamState"/>.</param> + /// <param name="isHeadRequest">Whether the current request is a HTTP HEAD request so only the headers get returned.</param> + /// <param name="httpContext">The current http context.</param> + /// <param name="transcodeManager">The <see cref="ITranscodeManager"/> singleton.</param> + /// <param name="ffmpegCommandLineArguments">The command line arguments to start ffmpeg.</param> + /// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param> + /// <param name="cancellationTokenSource">The <see cref="CancellationTokenSource"/>.</param> + /// <returns>A <see cref="Task{ActionResult}"/> containing the transcoded file.</returns> + public static async Task<ActionResult> GetTranscodedFile( + StreamState state, + bool isHeadRequest, + HttpContext httpContext, + ITranscodeManager transcodeManager, + string ffmpegCommandLineArguments, + TranscodingJobType transcodingJobType, + CancellationTokenSource cancellationTokenSource) + { + // Use the command line args with a dummy playlist path + var outputPath = state.OutputFilePath; + + httpContext.Response.Headers[HeaderNames.AcceptRanges] = "none"; + + var contentType = state.GetMimeType(outputPath); + + // Headers only + if (isHeadRequest) + { + httpContext.Response.Headers[HeaderNames.ContentType] = contentType; + return new OkResult(); + } + + using (await transcodeManager.LockAsync(outputPath, cancellationTokenSource.Token).ConfigureAwait(false)) + { + TranscodingJob? job; + if (!File.Exists(outputPath)) + { + job = await transcodeManager.StartFfMpeg( + state, + outputPath, + ffmpegCommandLineArguments, + httpContext.User.GetUserId(), + transcodingJobType, + cancellationTokenSource).ConfigureAwait(false); + } + else + { + job = transcodeManager.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive); + state.Dispose(); + } + + var stream = new ProgressiveFileStream(outputPath, job, transcodeManager); + return new FileStreamResult(stream, contentType); + } + } +} diff --git a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs new file mode 100644 index 00000000..cf42d5f1 --- /dev/null +++ b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs @@ -0,0 +1,349 @@ +using System; +using System.Globalization; +using System.Text; + +namespace Jellyfin.Api.Helpers; + +/// <summary> +/// Helpers to generate HLS codec strings according to +/// <a href="https://datatracker.ietf.org/doc/html/rfc6381#section-3.3">RFC 6381 section 3.3</a> +/// and the <a href="https://mp4ra.org">MP4 Registration Authority</a>. +/// </summary> +public static class HlsCodecStringHelpers +{ + /// <summary> + /// Codec name for MP3. + /// </summary> + public const string MP3 = "mp4a.40.34"; + + /// <summary> + /// Codec name for AC-3. + /// </summary> + public const string AC3 = "ac-3"; + + /// <summary> + /// Codec name for E-AC-3. + /// </summary> + public const string EAC3 = "ec-3"; + + /// <summary> + /// Codec name for FLAC. + /// </summary> + public const string FLAC = "fLaC"; + + /// <summary> + /// Codec name for ALAC. + /// </summary> + public const string ALAC = "alac"; + + /// <summary> + /// Codec name for OPUS. + /// </summary> + public const string OPUS = "Opus"; + + /// <summary> + /// Codec name for TRUEHD. + /// </summary> + public const string TRUEHD = "mlpa"; + + /// <summary> + /// Gets a MP3 codec string. + /// </summary> + /// <returns>MP3 codec string.</returns> + public static string GetMP3String() + { + return MP3; + } + + /// <summary> + /// Gets an AAC codec string. + /// </summary> + /// <param name="profile">AAC profile.</param> + /// <returns>AAC codec string.</returns> + public static string GetAACString(string? profile) + { + StringBuilder result = new StringBuilder("mp4a", 9); + + if (string.Equals(profile, "HE-AAC", StringComparison.OrdinalIgnoreCase)) + { + result.Append(".40.5"); + } + else + { + // Default to LC if profile is invalid + result.Append(".40.2"); + } + + return result.ToString(); + } + + /// <summary> + /// Gets an AC-3 codec string. + /// </summary> + /// <returns>AC-3 codec string.</returns> + public static string GetAC3String() + { + return AC3; + } + + /// <summary> + /// Gets an E-AC-3 codec string. + /// </summary> + /// <returns>E-AC-3 codec string.</returns> + public static string GetEAC3String() + { + return EAC3; + } + + /// <summary> + /// Gets an FLAC codec string. + /// </summary> + /// <returns>FLAC codec string.</returns> + public static string GetFLACString() + { + return FLAC; + } + + /// <summary> + /// Gets an ALAC codec string. + /// </summary> + /// <returns>ALAC codec string.</returns> + public static string GetALACString() + { + return ALAC; + } + + /// <summary> + /// Gets an OPUS codec string. + /// </summary> + /// <returns>OPUS codec string.</returns> + public static string GetOPUSString() + { + return OPUS; + } + + /// <summary> + /// Gets an TRUEHD codec string. + /// </summary> + /// <returns>TRUEHD codec string.</returns> + public static string GetTRUEHDString() + { + return TRUEHD; + } + + /// <summary> + /// Gets an DTS codec string. + /// </summary> + /// <param name="profile">DTS profile.</param> + /// <returns>DTS codec string.</returns> + public static string GetDTSString(string? profile) + { + if (string.Equals(profile, "DTS", StringComparison.OrdinalIgnoreCase) + || string.Equals(profile, "DTS-ES", StringComparison.OrdinalIgnoreCase) + || string.Equals(profile, "DTS 96/24", StringComparison.OrdinalIgnoreCase)) + { + return "dtsc"; + } + + if (string.Equals(profile, "DTS-HD HRA", StringComparison.OrdinalIgnoreCase) + || string.Equals(profile, "DTS-HD MA", StringComparison.OrdinalIgnoreCase) + || string.Equals(profile, "DTS-HD MA + DTS:X", StringComparison.OrdinalIgnoreCase) + || string.Equals(profile, "DTS-HD MA + DTS:X IMAX", StringComparison.OrdinalIgnoreCase)) + { + return "dtsh"; + } + + if (string.Equals(profile, "DTS Express", StringComparison.OrdinalIgnoreCase)) + { + return "dtse"; + } + + // Default to DTS core if profile is invalid + return "dtsc"; + } + + /// <summary> + /// Gets a H.264 codec string. + /// </summary> + /// <param name="profile">H.264 profile.</param> + /// <param name="level">H.264 level.</param> + /// <returns>H.264 string.</returns> + public static string GetH264String(string? profile, int level) + { + StringBuilder result = new StringBuilder("avc1", 11); + + if (string.Equals(profile, "high", StringComparison.OrdinalIgnoreCase)) + { + result.Append(".6400"); + } + else if (string.Equals(profile, "main", StringComparison.OrdinalIgnoreCase)) + { + result.Append(".4D40"); + } + else if (string.Equals(profile, "baseline", StringComparison.OrdinalIgnoreCase)) + { + result.Append(".42E0"); + } + else + { + // Default to constrained baseline if profile is invalid + result.Append(".4240"); + } + + string levelHex = level.ToString("X2", CultureInfo.InvariantCulture); + result.Append(levelHex); + + return result.ToString(); + } + + /// <summary> + /// Gets a H.265 codec string. + /// </summary> + /// <param name="profile">H.265 profile.</param> + /// <param name="level">H.265 level.</param> + /// <returns>H.265 string.</returns> + public static string GetH265String(string? profile, int level) + { + // The h265 syntax is a bit of a mystery at the time this comment was written. + // This is what I've found through various sources: + // FORMAT: [codecTag].[profile].[constraint?].L[level * 30].[UNKNOWN] + StringBuilder result = new StringBuilder("hvc1", 16); + + if (string.Equals(profile, "main10", StringComparison.OrdinalIgnoreCase) + || string.Equals(profile, "main 10", StringComparison.OrdinalIgnoreCase)) + { + result.Append(".2.4"); + } + else + { + // Default to main if profile is invalid + result.Append(".1.4"); + } + + result.Append(".L") + .Append(level) + .Append(".B0"); + + return result.ToString(); + } + + /// <summary> + /// Gets a VP9 codec string. + /// </summary> + /// <param name="width">Video width.</param> + /// <param name="height">Video height.</param> + /// <param name="pixelFormat">Video pixel format.</param> + /// <param name="framerate">Video framerate.</param> + /// <param name="bitDepth">Video bitDepth.</param> + /// <returns>The VP9 codec string.</returns> + public static string GetVp9String(int width, int height, string pixelFormat, float framerate, int bitDepth) + { + // refer: https://www.webmproject.org/vp9/mp4/ + StringBuilder result = new StringBuilder("vp09", 13); + + var profileString = pixelFormat switch + { + "yuv420p" => "00", + "yuvj420p" => "00", + "yuv422p" => "01", + "yuv444p" => "01", + "yuv420p10le" => "02", + "yuv420p12le" => "02", + "yuv422p10le" => "03", + "yuv422p12le" => "03", + "yuv444p10le" => "03", + "yuv444p12le" => "03", + _ => "00" + }; + + var lumaPictureSize = width * height; + var lumaSampleRate = lumaPictureSize * framerate; + var levelString = lumaPictureSize switch + { + <= 0 => "00", + <= 36864 => "10", + <= 73728 => "11", + <= 122880 => "20", + <= 245760 => "21", + <= 552960 => "30", + <= 983040 => "31", + <= 2228224 => lumaSampleRate <= 83558400 ? "40" : "41", + <= 8912896 => lumaSampleRate <= 311951360 ? "50" : (lumaSampleRate <= 588251136 ? "51" : "52"), + <= 35651584 => lumaSampleRate <= 1176502272 ? "60" : (lumaSampleRate <= 4706009088 ? "61" : "62"), + _ => "00" // This should not happen + }; + + if (bitDepth != 8 + && bitDepth != 10 + && bitDepth != 12) + { + // Default to 8 bits + bitDepth = 8; + } + + result.Append('.').Append(profileString).Append('.').Append(levelString); + var bitDepthD2 = bitDepth.ToString("D2", CultureInfo.InvariantCulture); + result.Append('.') + .Append(bitDepthD2); + + return result.ToString(); + } + + /// <summary> + /// Gets an AV1 codec string. + /// </summary> + /// <param name="profile">AV1 profile.</param> + /// <param name="level">AV1 level.</param> + /// <param name="tierFlag">AV1 tier flag.</param> + /// <param name="bitDepth">AV1 bit depth.</param> + /// <returns>The AV1 codec string.</returns> + public static string GetAv1String(string? profile, int level, bool tierFlag, int bitDepth) + { + // https://aomediacodec.github.io/av1-isobmff/#codecsparam + // FORMAT: [codecTag].[profile].[level][tier].[bitDepth] + StringBuilder result = new StringBuilder("av01", 13); + + if (string.Equals(profile, "Main", StringComparison.OrdinalIgnoreCase)) + { + result.Append(".0"); + } + else if (string.Equals(profile, "High", StringComparison.OrdinalIgnoreCase)) + { + result.Append(".1"); + } + else if (string.Equals(profile, "Professional", StringComparison.OrdinalIgnoreCase)) + { + result.Append(".2"); + } + else + { + // Default to Main + result.Append(".0"); + } + + if (level is <= 0 or > 31) + { + // Default to the maximum defined level 6.3 + level = 19; + } + + if (bitDepth != 8 + && bitDepth != 10 + && bitDepth != 12) + { + // Default to 8 bits + bitDepth = 8; + } + + result.Append('.') + // Needed to pad it double digits; otherwise, browsers will reject the stream. + .AppendFormat(CultureInfo.InvariantCulture, "{0:D2}", level) + .Append(tierFlag ? 'H' : 'M'); + + string bitDepthD2 = bitDepth.ToString("D2", CultureInfo.InvariantCulture); + result.Append('.') + .Append(bitDepthD2); + + return result.ToString(); + } +} diff --git a/Jellyfin.Api/Helpers/HlsHelpers.cs b/Jellyfin.Api/Helpers/HlsHelpers.cs new file mode 100644 index 00000000..15540338 --- /dev/null +++ b/Jellyfin.Api/Helpers/HlsHelpers.cs @@ -0,0 +1,132 @@ +using System; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Models.StreamingDtos; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; +using MediaBrowser.Model.IO; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Helpers; + +/// <summary> +/// The hls helpers. +/// </summary> +public static class HlsHelpers +{ + /// <summary> + /// Waits for a minimum number of segments to be available. + /// </summary> + /// <param name="playlist">The playlist string.</param> + /// <param name="segmentCount">The segment count.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param> + /// <returns>A <see cref="Task"/> indicating the waiting process.</returns> + public static async Task WaitForMinimumSegmentCount(string playlist, int? segmentCount, ILogger logger, CancellationToken cancellationToken) + { + logger.LogDebug("Waiting for {0} segments in {1}", segmentCount, playlist); + + while (!cancellationToken.IsCancellationRequested) + { + try + { + // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written + var fileStream = new FileStream( + playlist, + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite, + IODefaults.FileStreamBufferSize, + FileOptions.Asynchronous | FileOptions.SequentialScan); + await using (fileStream.ConfigureAwait(false)) + { + using var reader = new StreamReader(fileStream); + var count = 0; + + string? line; + while ((line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false)) is not null) + { + if (line.Contains("#EXTINF:", StringComparison.OrdinalIgnoreCase)) + { + count++; + if (count >= segmentCount) + { + logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist); + return; + } + } + } + } + + await Task.Delay(100, cancellationToken).ConfigureAwait(false); + } + catch (IOException) + { + // May get an error if the file is locked + } + + await Task.Delay(50, cancellationToken).ConfigureAwait(false); + } + } + + /// <summary> + /// Gets the #EXT-X-MAP string. + /// </summary> + /// <param name="outputPath">The output path of the file.</param> + /// <param name="state">The <see cref="StreamState"/>.</param> + /// <param name="isOsDepends">Get a normal string or depends on OS.</param> + /// <returns>The string text of #EXT-X-MAP.</returns> + public static string GetFmp4InitFileName(string outputPath, StreamState state, bool isOsDepends) + { + var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)); + var outputFileNameWithoutExtension = Path.GetFileNameWithoutExtension(outputPath); + var outputPrefix = Path.Combine(directory, outputFileNameWithoutExtension); + var outputExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer); + + // on Linux/Unix + // #EXT-X-MAP:URI="prefix-1.mp4" + var fmp4InitFileName = outputFileNameWithoutExtension + "-1" + outputExtension; + if (!isOsDepends) + { + return fmp4InitFileName; + } + + if (OperatingSystem.IsWindows()) + { + // on Windows + // #EXT-X-MAP:URI="X:\transcodes\prefix-1.mp4" + fmp4InitFileName = outputPrefix + "-1" + outputExtension; + } + + return fmp4InitFileName; + } + + /// <summary> + /// Gets the hls playlist text. + /// </summary> + /// <param name="path">The path to the playlist file.</param> + /// <param name="state">The <see cref="StreamState"/>.</param> + /// <returns>The playlist text as a string.</returns> + public static string GetLivePlaylistText(string path, StreamState state) + { + var text = File.ReadAllText(path); + + var segmentFormat = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer).TrimStart('.'); + if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)) + { + var fmp4InitFileName = GetFmp4InitFileName(path, state, true); + var baseUrlParam = string.Format( + CultureInfo.InvariantCulture, + "hls/{0}/", + Path.GetFileNameWithoutExtension(path)); + var newFmp4InitFileName = baseUrlParam + GetFmp4InitFileName(path, state, false); + + // Replace fMP4 init file URI. + text = text.Replace(fmp4InitFileName, newFmp4InitFileName, StringComparison.InvariantCulture); + } + + return text; + } +} diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs new file mode 100644 index 00000000..454d3f08 --- /dev/null +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -0,0 +1,518 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Security.Claims; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Extensions; +using Jellyfin.Data; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Session; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Helpers; + +/// <summary> +/// Media info helper. +/// </summary> +public class MediaInfoHelper +{ + private readonly IUserManager _userManager; + private readonly ILibraryManager _libraryManager; + private readonly IMediaSourceManager _mediaSourceManager; + private readonly IMediaEncoder _mediaEncoder; + private readonly IServerConfigurationManager _serverConfigurationManager; + private readonly ILogger<MediaInfoHelper> _logger; + private readonly INetworkManager _networkManager; + private readonly IDeviceManager _deviceManager; + + /// <summary> + /// Initializes a new instance of the <see cref="MediaInfoHelper"/> class. + /// </summary> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> + /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="logger">Instance of the <see cref="ILogger{MediaInfoHelper}"/> interface.</param> + /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param> + /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param> + public MediaInfoHelper( + IUserManager userManager, + ILibraryManager libraryManager, + IMediaSourceManager mediaSourceManager, + IMediaEncoder mediaEncoder, + IServerConfigurationManager serverConfigurationManager, + ILogger<MediaInfoHelper> logger, + INetworkManager networkManager, + IDeviceManager deviceManager) + { + _userManager = userManager; + _libraryManager = libraryManager; + _mediaSourceManager = mediaSourceManager; + _mediaEncoder = mediaEncoder; + _serverConfigurationManager = serverConfigurationManager; + _logger = logger; + _networkManager = networkManager; + _deviceManager = deviceManager; + } + + /// <summary> + /// Get playback info. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="user">The user.</param> + /// <param name="mediaSourceId">Media source id.</param> + /// <param name="liveStreamId">Live stream id.</param> + /// <returns>A <see cref="Task"/> containing the <see cref="PlaybackInfoResponse"/>.</returns> + public async Task<PlaybackInfoResponse> GetPlaybackInfo( + BaseItem item, + User? user, + string? mediaSourceId = null, + string? liveStreamId = null) + { + var result = new PlaybackInfoResponse(); + + MediaSourceInfo[] mediaSources; + if (string.IsNullOrWhiteSpace(liveStreamId)) + { + // TODO (moved from MediaBrowser.Api) handle supportedLiveMediaTypes? + var mediaSourcesList = await _mediaSourceManager.GetPlaybackMediaSources(item, user, true, true, CancellationToken.None).ConfigureAwait(false); + + if (string.IsNullOrWhiteSpace(mediaSourceId)) + { + mediaSources = mediaSourcesList.ToArray(); + } + else + { + mediaSources = mediaSourcesList + .Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)) + .ToArray(); + } + } + else + { + var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false); + + mediaSources = new[] { mediaSource }; + } + + if (mediaSources.Length == 0) + { + result.MediaSources = Array.Empty<MediaSourceInfo>(); + + result.ErrorCode ??= PlaybackErrorCode.NoCompatibleStream; + } + else + { + // Since we're going to be setting properties on MediaSourceInfos that come out of _mediaSourceManager, we should clone it + // Should we move this directly into MediaSourceManager? + var mediaSourcesClone = JsonSerializer.Deserialize<MediaSourceInfo[]>(JsonSerializer.SerializeToUtf8Bytes(mediaSources)); + if (mediaSourcesClone is not null) + { + // Carry over the default audio index source. + // This field is not intended to be exposed to API clients, but it is used internally by the server + for (int i = 0; i < mediaSourcesClone.Length && i < mediaSources.Length; i++) + { + mediaSourcesClone[i].DefaultAudioIndexSource = mediaSources[i].DefaultAudioIndexSource; + } + + result.MediaSources = mediaSourcesClone; + } + + result.PlaySessionId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); + } + + return result; + } + + /// <summary> + /// SetDeviceSpecificData. + /// </summary> + /// <param name="item">Item to set data for.</param> + /// <param name="mediaSource">Media source info.</param> + /// <param name="profile">Device profile.</param> + /// <param name="claimsPrincipal">Current claims principal.</param> + /// <param name="maxBitrate">Max bitrate.</param> + /// <param name="startTimeTicks">Start time ticks.</param> + /// <param name="mediaSourceId">Media source id.</param> + /// <param name="audioStreamIndex">Audio stream index.</param> + /// <param name="subtitleStreamIndex">Subtitle stream index.</param> + /// <param name="maxAudioChannels">Max audio channels.</param> + /// <param name="playSessionId">Play session id.</param> + /// <param name="userId">User id.</param> + /// <param name="enableDirectPlay">Enable direct play.</param> + /// <param name="enableDirectStream">Enable direct stream.</param> + /// <param name="enableTranscoding">Enable transcoding.</param> + /// <param name="allowVideoStreamCopy">Allow video stream copy.</param> + /// <param name="allowAudioStreamCopy">Allow audio stream copy.</param> + /// <param name="alwaysBurnInSubtitleWhenTranscoding">Always burn-in subtitle when transcoding.</param> + /// <param name="ipAddress">Requesting IP address.</param> + public void SetDeviceSpecificData( + BaseItem item, + MediaSourceInfo mediaSource, + DeviceProfile profile, + ClaimsPrincipal claimsPrincipal, + int? maxBitrate, + long startTimeTicks, + string mediaSourceId, + int? audioStreamIndex, + int? subtitleStreamIndex, + int? maxAudioChannels, + string playSessionId, + Guid userId, + bool enableDirectPlay, + bool enableDirectStream, + bool enableTranscoding, + bool allowVideoStreamCopy, + bool allowAudioStreamCopy, + bool alwaysBurnInSubtitleWhenTranscoding, + IPAddress ipAddress) + { + var streamBuilder = new StreamBuilder(_mediaEncoder, _logger); + + var options = new MediaOptions + { + MediaSources = new[] { mediaSource }, + Context = EncodingContext.Streaming, + DeviceId = claimsPrincipal.GetDeviceId(), + ItemId = item.Id, + Profile = profile, + MaxAudioChannels = maxAudioChannels, + AllowAudioStreamCopy = allowAudioStreamCopy, + AllowVideoStreamCopy = allowVideoStreamCopy, + AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding, + }; + + if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase)) + { + options.MediaSourceId = mediaSourceId; + options.AudioStreamIndex = audioStreamIndex; + options.SubtitleStreamIndex = subtitleStreamIndex; + } + + var user = _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException(); + + if (!enableDirectPlay) + { + mediaSource.SupportsDirectPlay = false; + } + + if (!enableDirectStream || !allowVideoStreamCopy) + { + mediaSource.SupportsDirectStream = false; + } + + if (!enableTranscoding) + { + mediaSource.SupportsTranscoding = false; + } + + if (item is Audio) + { + _logger.LogInformation( + "User policy for {0}. EnableAudioPlaybackTranscoding: {1}", + user.Username, + user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)); + } + else + { + _logger.LogInformation( + "User policy for {0}. EnablePlaybackRemuxing: {1} EnableVideoPlaybackTranscoding: {2} EnableAudioPlaybackTranscoding: {3}", + user.Username, + user.HasPermission(PermissionKind.EnablePlaybackRemuxing), + user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding), + user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)); + } + + options.MaxBitrate = GetMaxBitrate(maxBitrate, user, ipAddress); + + if (!options.ForceDirectStream) + { + // direct-stream http streaming is currently broken + options.EnableDirectStream = false; + } + + // Beginning of Playback Determination + var streamInfo = item.MediaType == MediaType.Audio + ? streamBuilder.GetOptimalAudioStream(options) + : streamBuilder.GetOptimalVideoStream(options); + + if (streamInfo is not null) + { + streamInfo.PlaySessionId = playSessionId; + streamInfo.StartPositionTicks = startTimeTicks; + + mediaSource.SupportsDirectPlay = streamInfo.PlayMethod == PlayMethod.DirectPlay; + + // Players do not handle this being set according to PlayMethod + mediaSource.SupportsDirectStream = + options.EnableDirectStream + ? streamInfo.PlayMethod == PlayMethod.DirectPlay || streamInfo.PlayMethod == PlayMethod.DirectStream + : streamInfo.PlayMethod == PlayMethod.DirectPlay; + + mediaSource.SupportsTranscoding = + streamInfo.PlayMethod == PlayMethod.DirectStream + || mediaSource.TranscodingContainer is not null + || profile.TranscodingProfiles.Any(i => i.Type == streamInfo.MediaType && i.Context == options.Context); + + if (item is Audio) + { + if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)) + { + mediaSource.SupportsTranscoding = false; + } + } + else if (item is Video) + { + if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding) + && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding) + && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing)) + { + mediaSource.SupportsTranscoding = false; + } + } + + if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding)) + { + mediaSource.SupportsDirectPlay = false; + mediaSource.SupportsDirectStream = false; + + mediaSource.TranscodingUrl = streamInfo.ToUrl(null, claimsPrincipal.GetToken(), "&allowVideoStreamCopy=false&allowAudioStreamCopy=false"); + mediaSource.TranscodingContainer = streamInfo.Container; + mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; + if (streamInfo.AlwaysBurnInSubtitleWhenTranscoding) + { + mediaSource.TranscodingUrl += "&alwaysBurnInSubtitleWhenTranscoding=true"; + } + } + else + { + if (!mediaSource.SupportsDirectPlay && (mediaSource.SupportsTranscoding || mediaSource.SupportsDirectStream)) + { + streamInfo.PlayMethod = PlayMethod.Transcode; + mediaSource.TranscodingUrl = streamInfo.ToUrl(null, claimsPrincipal.GetToken(), null); + + if (!allowVideoStreamCopy) + { + mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false"; + } + + if (!allowAudioStreamCopy) + { + mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false"; + } + + if (streamInfo.AlwaysBurnInSubtitleWhenTranscoding) + { + mediaSource.TranscodingUrl += "&alwaysBurnInSubtitleWhenTranscoding=true"; + } + } + } + + // Do this after the above so that StartPositionTicks is set + // The token must not be null + SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, claimsPrincipal.GetToken()!); + mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex; + } + + foreach (var attachment in mediaSource.MediaAttachments) + { + attachment.DeliveryUrl = string.Format( + CultureInfo.InvariantCulture, + "/Videos/{0}/{1}/Attachments/{2}", + item.Id, + mediaSource.Id, + attachment.Index); + } + } + + /// <summary> + /// Sort media source. + /// </summary> + /// <param name="result">Playback info response.</param> + /// <param name="maxBitrate">Max bitrate.</param> + public void SortMediaSources(PlaybackInfoResponse result, long? maxBitrate) + { + var originalList = result.MediaSources.ToList(); + + result.MediaSources = result.MediaSources.OrderBy(i => + { + // Nothing beats direct playing a file + if (i.SupportsDirectPlay && i.Protocol == MediaProtocol.File) + { + return 0; + } + + return 1; + }) + .ThenBy(i => + { + // Let's assume direct streaming a file is just as desirable as direct playing a remote url + if (i.SupportsDirectPlay || i.SupportsDirectStream) + { + return 0; + } + + return 1; + }) + .ThenBy(i => + { + return i.Protocol switch + { + MediaProtocol.File => 0, + _ => 1, + }; + }) + .ThenBy(i => + { + if (maxBitrate.HasValue && i.Bitrate.HasValue) + { + return i.Bitrate.Value <= maxBitrate.Value ? 0 : 2; + } + + return 1; + }) + .ThenBy(originalList.IndexOf) + .ToArray(); + } + + /// <summary> + /// Open media source. + /// </summary> + /// <param name="httpContext">Http Context.</param> + /// <param name="request">Live stream request.</param> + /// <returns>A <see cref="Task"/> containing the <see cref="LiveStreamResponse"/>.</returns> + public async Task<LiveStreamResponse> OpenMediaSource(HttpContext httpContext, LiveStreamRequest request) + { + var result = await _mediaSourceManager.OpenLiveStream(request, CancellationToken.None).ConfigureAwait(false); + + var profile = request.DeviceProfile; + if (profile is null) + { + var clientCapabilities = _deviceManager.GetCapabilities(httpContext.User.GetDeviceId()); + if (clientCapabilities is not null) + { + profile = clientCapabilities.DeviceProfile; + } + } + + if (profile is not null) + { + var item = _libraryManager.GetItemById<BaseItem>(request.ItemId) + ?? throw new ResourceNotFoundException(); + + SetDeviceSpecificData( + item, + result.MediaSource, + profile, + httpContext.User, + request.MaxStreamingBitrate, + request.StartTimeTicks ?? 0, + result.MediaSource.Id, + request.AudioStreamIndex, + request.SubtitleStreamIndex, + request.MaxAudioChannels, + request.PlaySessionId, + request.UserId, + request.EnableDirectPlay, + request.EnableDirectStream, + true, + true, + true, + request.AlwaysBurnInSubtitleWhenTranscoding, + httpContext.GetNormalizedRemoteIP()); + } + else + { + if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl)) + { + result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId; + } + } + + // here was a check if (result.MediaSource is not null) but Rider said it will never be null + NormalizeMediaSourceContainer(result.MediaSource, profile!, DlnaProfileType.Video); + + return result; + } + + /// <summary> + /// Normalize media source container. + /// </summary> + /// <param name="mediaSource">Media source.</param> + /// <param name="profile">Device profile.</param> + /// <param name="type">Dlna profile type.</param> + public void NormalizeMediaSourceContainer(MediaSourceInfo mediaSource, DeviceProfile profile, DlnaProfileType type) + { + mediaSource.Container = StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(mediaSource.Container, profile, type); + } + + private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string accessToken) + { + var profiles = info.GetSubtitleProfiles(_mediaEncoder, false, "-", accessToken); + mediaSource.DefaultSubtitleStreamIndex = info.SubtitleStreamIndex; + + mediaSource.TranscodeReasons = info.TranscodeReasons; + + foreach (var profile in profiles) + { + foreach (var stream in mediaSource.MediaStreams) + { + if (stream.Type == MediaStreamType.Subtitle && stream.Index == profile.Index) + { + stream.DeliveryMethod = profile.DeliveryMethod; + + if (profile.DeliveryMethod == SubtitleDeliveryMethod.External) + { + stream.DeliveryUrl = profile.Url.TrimStart('-'); + stream.IsExternalUrl = profile.IsExternalUrl; + } + } + } + } + } + + private int? GetMaxBitrate(int? clientMaxBitrate, User user, IPAddress ipAddress) + { + var maxBitrate = clientMaxBitrate; + var remoteClientMaxBitrate = user.RemoteClientBitrateLimit ?? 0; + + if (remoteClientMaxBitrate <= 0) + { + remoteClientMaxBitrate = _serverConfigurationManager.Configuration.RemoteClientBitrateLimit; + } + + if (remoteClientMaxBitrate > 0) + { + var isInLocalNetwork = _networkManager.IsInLocalNetwork(ipAddress); + + _logger.LogInformation("RemoteClientBitrateLimit: {0}, RemoteIP: {1}, IsInLocalNetwork: {2}", remoteClientMaxBitrate, ipAddress, isInLocalNetwork); + if (!isInLocalNetwork) + { + maxBitrate = Math.Min(maxBitrate ?? remoteClientMaxBitrate, remoteClientMaxBitrate); + } + } + + return maxBitrate; + } +} diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs new file mode 100644 index 00000000..5072f902 --- /dev/null +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using Jellyfin.Api.Extensions; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Api.Helpers; + +/// <summary> +/// Request Extensions. +/// </summary> +public static class RequestHelpers +{ + /// <summary> + /// Get Order By. + /// </summary> + /// <param name="sortBy">Sort By. Comma delimited string.</param> + /// <param name="requestedSortOrder">Sort Order. Comma delimited string.</param> + /// <returns>Order By.</returns> + public static (ItemSortBy, SortOrder)[] GetOrderBy(IReadOnlyList<ItemSortBy> sortBy, IReadOnlyList<SortOrder> requestedSortOrder) + { + if (sortBy.Count == 0) + { + return Array.Empty<(ItemSortBy, SortOrder)>(); + } + + var result = new (ItemSortBy, SortOrder)[sortBy.Count]; + var i = 0; + // Add elements which have a SortOrder specified + for (; i < requestedSortOrder.Count; i++) + { + result[i] = (sortBy[i], requestedSortOrder[i]); + } + + // Add remaining elements with the first specified SortOrder + // or the default one if no SortOrders are specified + var order = requestedSortOrder.Count > 0 ? requestedSortOrder[0] : SortOrder.Ascending; + for (; i < sortBy.Count; i++) + { + result[i] = (sortBy[i], order); + } + + return result; + } + + /// <summary> + /// Checks if the user can access a user. + /// </summary> + /// <param name="claimsPrincipal">The <see cref="ClaimsPrincipal"/> for the current request.</param> + /// <param name="userId">The user id.</param> + /// <returns>A <see cref="bool"/> whether the user can access the user.</returns> + internal static Guid GetUserId(ClaimsPrincipal claimsPrincipal, Guid? userId) + { + var authenticatedUserId = claimsPrincipal.GetUserId(); + + // UserId not provided, fall back to authenticated user id. + if (userId.IsNullOrEmpty()) + { + return authenticatedUserId; + } + + // User must be administrator to access another user. + var isAdministrator = claimsPrincipal.IsInRole(UserRoles.Administrator); + if (!userId.Value.Equals(authenticatedUserId) && !isAdministrator) + { + throw new SecurityException("Forbidden"); + } + + return userId.Value; + } + + /// <summary> + /// Checks if the user can update an entry. + /// </summary> + /// <param name="claimsPrincipal">The <see cref="ClaimsPrincipal"/> for the current request.</param> + /// <param name="user">The user id.</param> + /// <param name="restrictUserPreferences">Whether to restrict the user preferences.</param> + /// <returns>A <see cref="bool"/> whether the user can update the entry.</returns> + internal static bool AssertCanUpdateUser(ClaimsPrincipal claimsPrincipal, User user, bool restrictUserPreferences) + { + var authenticatedUserId = claimsPrincipal.GetUserId(); + var isAdministrator = claimsPrincipal.IsInRole(UserRoles.Administrator); + + // If they're going to update the record of another user, they must be an administrator + if (!user.Id.Equals(authenticatedUserId) && !isAdministrator) + { + return false; + } + + // TODO the EnableUserPreferenceAccess policy does not seem to be used elsewhere + if (!restrictUserPreferences || isAdministrator) + { + return true; + } + + return user.EnableUserPreferenceAccess; + } + + /// <summary> + /// Get the session based on http request. + /// </summary> + /// <param name="sessionManager">The session manager.</param> + /// <param name="userManager">The user manager.</param> + /// <param name="httpContext">The http context.</param> + /// <param name="userId">The optional userid.</param> + /// <returns>The session.</returns> + /// <exception cref="ResourceNotFoundException">Session not found.</exception> + public static async Task<SessionInfo> GetSession(ISessionManager sessionManager, IUserManager userManager, HttpContext httpContext, Guid? userId = null) + { + userId ??= httpContext.User.GetUserId(); + User? user = null; + if (!userId.IsNullOrEmpty()) + { + user = userManager.GetUserById(userId.Value); + } + + var session = await sessionManager.LogSessionActivity( + httpContext.User.GetClient(), + httpContext.User.GetVersion(), + httpContext.User.GetDeviceId(), + httpContext.User.GetDevice(), + httpContext.GetNormalizedRemoteIP().ToString(), + user).ConfigureAwait(false); + + if (session is null) + { + throw new ResourceNotFoundException("Session not found."); + } + + return session; + } + + internal static async Task<string> GetSessionId(ISessionManager sessionManager, IUserManager userManager, HttpContext httpContext) + { + var session = await GetSession(sessionManager, userManager, httpContext).ConfigureAwait(false); + + return session.Id; + } + + internal static QueryResult<BaseItemDto> CreateQueryResult( + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> result, + DtoOptions dtoOptions, + IDtoService dtoService, + bool includeItemTypes, + User? user) + { + var dtos = result.Items.Select(i => + { + var (baseItem, counts) = i; + var dto = dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user); + + if (includeItemTypes) + { + dto.ChildCount = counts.ItemCount; + dto.ProgramCount = counts.ProgramCount; + dto.SeriesCount = counts.SeriesCount; + dto.EpisodeCount = counts.EpisodeCount; + dto.MovieCount = counts.MovieCount; + dto.TrailerCount = counts.TrailerCount; + dto.AlbumCount = counts.AlbumCount; + dto.SongCount = counts.SongCount; + dto.ArtistCount = counts.ArtistCount; + } + + return dto; + }); + + return new QueryResult<BaseItemDto>( + result.StartIndex, + result.TotalRecordCount, + dtos.ToArray()); + } +} diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs new file mode 100644 index 00000000..1e984542 --- /dev/null +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -0,0 +1,607 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Extensions; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.Net.Http.Headers; + +namespace Jellyfin.Api.Helpers; + +/// <summary> +/// The streaming helpers. +/// </summary> +public static class StreamingHelpers +{ + /// <summary> + /// Gets the current streaming state. + /// </summary> + /// <param name="streamingRequest">The <see cref="StreamingRequestDto"/>.</param> + /// <param name="httpContext">The <see cref="HttpContext"/>.</param> + /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> + /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param> + /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param> + /// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param> + /// <returns>A <see cref="Task"/> containing the current <see cref="StreamState"/>.</returns> + public static async Task<StreamState> GetStreamingState( + StreamingRequestDto streamingRequest, + HttpContext httpContext, + IMediaSourceManager mediaSourceManager, + IUserManager userManager, + ILibraryManager libraryManager, + IServerConfigurationManager serverConfigurationManager, + IMediaEncoder mediaEncoder, + EncodingHelper encodingHelper, + ITranscodeManager transcodeManager, + TranscodingJobType transcodingJobType, + CancellationToken cancellationToken) + { + var httpRequest = httpContext.Request; + if (!string.IsNullOrWhiteSpace(streamingRequest.Params)) + { + ParseParams(streamingRequest); + } + + streamingRequest.StreamOptions = ParseStreamOptions(httpRequest.Query); + if (httpRequest.Path.Value is null) + { + throw new ResourceNotFoundException(nameof(httpRequest.Path)); + } + + var url = httpRequest.Path.Value.AsSpan().RightPart('.').ToString(); + + if (string.IsNullOrEmpty(streamingRequest.AudioCodec)) + { + streamingRequest.AudioCodec = encodingHelper.InferAudioCodec(url); + } + + var state = new StreamState(mediaSourceManager, transcodingJobType, transcodeManager) + { + Request = streamingRequest, + RequestedUrl = url, + UserAgent = httpRequest.Headers[HeaderNames.UserAgent] + }; + + var userId = httpContext.User.GetUserId(); + if (!userId.IsEmpty()) + { + state.User = userManager.GetUserById(userId); + } + + if (state.IsVideoRequest && !string.IsNullOrWhiteSpace(state.Request.VideoCodec)) + { + state.SupportedVideoCodecs = state.Request.VideoCodec.Split(',', StringSplitOptions.RemoveEmptyEntries); + state.Request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(); + } + + if (!string.IsNullOrWhiteSpace(streamingRequest.AudioCodec)) + { + state.SupportedAudioCodecs = streamingRequest.AudioCodec.Split(',', StringSplitOptions.RemoveEmptyEntries); + state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(mediaEncoder.CanEncodeToAudioCodec) + ?? state.SupportedAudioCodecs.FirstOrDefault(); + } + + if (!string.IsNullOrWhiteSpace(streamingRequest.SubtitleCodec)) + { + state.SupportedSubtitleCodecs = streamingRequest.SubtitleCodec.Split(',', StringSplitOptions.RemoveEmptyEntries); + state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(mediaEncoder.CanEncodeToSubtitleCodec) + ?? state.SupportedSubtitleCodecs.FirstOrDefault(); + } + + var item = libraryManager.GetItemById<BaseItem>(streamingRequest.Id) + ?? throw new ResourceNotFoundException(); + + state.IsInputVideo = item.MediaType == MediaType.Video; + + MediaSourceInfo? mediaSource = null; + if (string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId)) + { + var currentJob = !string.IsNullOrWhiteSpace(streamingRequest.PlaySessionId) + ? transcodeManager.GetTranscodingJob(streamingRequest.PlaySessionId) + : null; + + if (currentJob is not null) + { + mediaSource = currentJob.MediaSource; + } + + if (mediaSource is null) + { + var mediaSources = await mediaSourceManager.GetPlaybackMediaSources(libraryManager.GetItemById<BaseItem>(streamingRequest.Id), null, false, false, cancellationToken).ConfigureAwait(false); + + mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId) + ? mediaSources[0] + : mediaSources.FirstOrDefault(i => string.Equals(i.Id, streamingRequest.MediaSourceId, StringComparison.Ordinal)); + + if (mediaSource is null && Guid.Parse(streamingRequest.MediaSourceId).Equals(streamingRequest.Id)) + { + mediaSource = mediaSources[0]; + } + } + } + else + { + var liveStreamInfo = await mediaSourceManager.GetLiveStreamWithDirectStreamProvider(streamingRequest.LiveStreamId, cancellationToken).ConfigureAwait(false); + mediaSource = liveStreamInfo.Item1; + state.DirectStreamProvider = liveStreamInfo.Item2; + + // Cap the max bitrate when it is too high. This is usually due to ffmpeg is unable to probe the source liveTV streams' bitrate. + if (mediaSource.FallbackMaxStreamingBitrate is not null && streamingRequest.VideoBitRate is not null) + { + streamingRequest.VideoBitRate = Math.Min(streamingRequest.VideoBitRate.Value, mediaSource.FallbackMaxStreamingBitrate.Value); + } + } + + var encodingOptions = serverConfigurationManager.GetEncodingOptions(); + + encodingHelper.AttachMediaSourceInfo(state, encodingOptions, mediaSource, url); + + string? containerInternal = Path.GetExtension(state.RequestedUrl); + + if (string.IsNullOrEmpty(containerInternal) + && (!string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId) + || (mediaSource != null && mediaSource.IsInfiniteStream))) + { + containerInternal = ".ts"; + } + + if (!string.IsNullOrEmpty(streamingRequest.Container)) + { + containerInternal = streamingRequest.Container; + } + + if (string.IsNullOrEmpty(containerInternal)) + { + containerInternal = streamingRequest.Static ? + StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, null, DlnaProfileType.Audio) + : GetOutputFileExtension(state, mediaSource); + } + + var outputAudioCodec = streamingRequest.AudioCodec; + state.OutputAudioCodec = outputAudioCodec; + state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.'); + state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec); + if (EncodingHelper.LosslessAudioCodecs.Contains(outputAudioCodec)) + { + state.OutputAudioBitrate = state.AudioStream.BitRate ?? 0; + } + else + { + state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream, state.OutputAudioChannels) ?? 0; + } + + if (outputAudioCodec.StartsWith("pcm_", StringComparison.Ordinal)) + { + containerInternal = ".pcm"; + } + + if (state.VideoRequest is not null) + { + state.OutputVideoCodec = state.Request.VideoCodec; + state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); + + encodingHelper.TryStreamCopy(state, encodingOptions); + + if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && state.OutputVideoBitrate.HasValue) + { + var isVideoResolutionNotRequested = !state.VideoRequest.Width.HasValue + && !state.VideoRequest.Height.HasValue + && !state.VideoRequest.MaxWidth.HasValue + && !state.VideoRequest.MaxHeight.HasValue; + + if (isVideoResolutionNotRequested + && state.VideoStream is not null + && state.VideoRequest.VideoBitRate.HasValue + && state.VideoStream.BitRate.HasValue + && state.VideoRequest.VideoBitRate.Value >= state.VideoStream.BitRate.Value) + { + // Don't downscale the resolution if the width/height/MaxWidth/MaxHeight is not requested, + // and the requested video bitrate is greater than source video bitrate. + if (state.VideoStream.Width.HasValue || state.VideoStream.Height.HasValue) + { + state.VideoRequest.MaxWidth = state.VideoStream?.Width; + state.VideoRequest.MaxHeight = state.VideoStream?.Height; + } + } + else + { + var h264EquivalentBitrate = EncodingHelper.ScaleBitrate( + state.OutputVideoBitrate.Value, + state.ActualOutputVideoCodec, + "h264"); + var resolution = ResolutionNormalizer.Normalize( + state.VideoStream?.BitRate, + state.OutputVideoBitrate.Value, + h264EquivalentBitrate, + state.VideoRequest.MaxWidth, + state.VideoRequest.MaxHeight, + state.TargetFramerate); + + state.VideoRequest.MaxWidth = resolution.MaxWidth; + state.VideoRequest.MaxHeight = resolution.MaxHeight; + } + } + + if (state.AudioStream is not null && !EncodingHelper.IsCopyCodec(state.OutputAudioCodec) && string.Equals(state.AudioStream.Codec, state.OutputAudioCodec, StringComparison.OrdinalIgnoreCase) && state.OutputAudioBitrate.HasValue) + { + state.OutputAudioCodec = state.SupportedAudioCodecs.Where(c => !EncodingHelper.LosslessAudioCodecs.Contains(c)).FirstOrDefault(mediaEncoder.CanEncodeToAudioCodec); + } + } + + var ext = string.IsNullOrWhiteSpace(state.OutputContainer) + ? GetOutputFileExtension(state, mediaSource) + : ("." + GetContainerFileExtension(state.OutputContainer)); + + state.OutputFilePath = GetOutputFilePath(state, ext, serverConfigurationManager, streamingRequest.DeviceId, streamingRequest.PlaySessionId); + + return state; + } + + /// <summary> + /// Parses query parameters as StreamOptions. + /// </summary> + /// <param name="queryString">The query string.</param> + /// <returns>A <see cref="Dictionary{String,String}"/> containing the stream options.</returns> + private static Dictionary<string, string?> ParseStreamOptions(IQueryCollection queryString) + { + Dictionary<string, string?> streamOptions = new Dictionary<string, string?>(); + foreach (var param in queryString) + { + if (char.IsLower(param.Key[0])) + { + // This was probably not parsed initially and should be a StreamOptions + // or the generated URL should correctly serialize it + // TODO: This should be incorporated either in the lower framework for parsing requests + streamOptions[param.Key] = param.Value; + } + } + + return streamOptions; + } + + /// <summary> + /// Gets the output file extension. + /// </summary> + /// <param name="state">The state.</param> + /// <param name="mediaSource">The mediaSource.</param> + /// <returns>System.String.</returns> + private static string GetOutputFileExtension(StreamState state, MediaSourceInfo? mediaSource) + { + var ext = Path.GetExtension(state.RequestedUrl); + if (!string.IsNullOrEmpty(ext)) + { + return ext; + } + + // Try to infer based on the desired video codec + if (state.IsVideoRequest) + { + var videoCodec = state.Request.VideoCodec; + + if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)) + { + return ".ts"; + } + + if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase)) + { + return ".mp4"; + } + + if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase)) + { + return ".ogv"; + } + + if (string.Equals(videoCodec, "vp8", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase)) + { + return ".webm"; + } + + if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase)) + { + return ".asf"; + } + } + else + { + // Try to infer based on the desired audio codec + var audioCodec = state.Request.AudioCodec; + + if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase)) + { + return ".aac"; + } + + if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase)) + { + return ".mp3"; + } + + if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase)) + { + return ".ogg"; + } + + if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase)) + { + return ".wma"; + } + } + + // Fallback to the container of mediaSource + if (!string.IsNullOrEmpty(mediaSource?.Container)) + { + var idx = mediaSource.Container.IndexOf(',', StringComparison.OrdinalIgnoreCase); + return '.' + (idx == -1 ? mediaSource.Container : mediaSource.Container[..idx]).Trim(); + } + + throw new InvalidOperationException("Failed to find an appropriate file extension"); + } + + /// <summary> + /// Gets the output file path for transcoding. + /// </summary> + /// <param name="state">The current <see cref="StreamState"/>.</param> + /// <param name="outputFileExtension">The file extension of the output file.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="deviceId">The device id.</param> + /// <param name="playSessionId">The play session id.</param> + /// <returns>The complete file path, including the folder, for the transcoding file.</returns> + private static string GetOutputFilePath(StreamState state, string outputFileExtension, IServerConfigurationManager serverConfigurationManager, string? deviceId, string? playSessionId) + { + var data = $"{state.MediaPath}-{state.UserAgent}-{deviceId!}-{playSessionId!}"; + + var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture); + var ext = outputFileExtension.ToLowerInvariant(); + var folder = serverConfigurationManager.GetTranscodePath(); + + return Path.Combine(folder, filename + ext); + } + + /// <summary> + /// Parses the parameters. + /// </summary> + /// <param name="request">The request.</param> + private static void ParseParams(StreamingRequestDto request) + { + if (string.IsNullOrEmpty(request.Params)) + { + return; + } + + var vals = request.Params.Split(';'); + + var videoRequest = request as VideoRequestDto; + + for (var i = 0; i < vals.Length; i++) + { + var val = vals[i]; + + if (string.IsNullOrWhiteSpace(val)) + { + continue; + } + + switch (i) + { + case 0: + // DeviceProfileId + break; + case 1: + request.DeviceId = val; + break; + case 2: + request.MediaSourceId = val; + break; + case 3: + request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + break; + case 4: + if (videoRequest is not null) + { + videoRequest.VideoCodec = val; + } + + break; + case 5: + request.AudioCodec = val; + break; + case 6: + if (videoRequest is not null) + { + videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture); + } + + break; + case 7: + if (videoRequest is not null) + { + videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture); + } + + break; + case 8: + if (videoRequest is not null) + { + videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture); + } + + break; + case 9: + request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture); + break; + case 10: + request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture); + break; + case 11: + if (videoRequest is not null) + { + videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture); + } + + break; + case 12: + if (videoRequest is not null) + { + videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture); + } + + break; + case 13: + if (videoRequest is not null) + { + videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture); + } + + break; + case 14: + request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture); + break; + case 15: + if (videoRequest is not null) + { + videoRequest.Level = val; + } + + break; + case 16: + if (videoRequest is not null) + { + videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture); + } + + break; + case 17: + if (videoRequest is not null) + { + videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture); + } + + break; + case 18: + if (videoRequest is not null) + { + videoRequest.Profile = val; + } + + break; + case 19: + // cabac no longer used + break; + case 20: + request.PlaySessionId = val; + break; + case 21: + // api_key + break; + case 22: + request.LiveStreamId = val; + break; + case 23: + // Duplicating ItemId because of MediaMonkey + break; + case 24: + if (videoRequest is not null) + { + videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } + + break; + case 25: + if (!string.IsNullOrWhiteSpace(val) && videoRequest is not null) + { + if (Enum.TryParse(val, out SubtitleDeliveryMethod method)) + { + videoRequest.SubtitleMethod = method; + } + } + + break; + case 26: + request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture); + break; + case 27: + if (videoRequest is not null) + { + videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } + + break; + case 28: + request.Tag = val; + break; + case 29: + if (videoRequest is not null) + { + videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } + + break; + case 30: + request.SubtitleCodec = val; + break; + case 31: + if (videoRequest is not null) + { + videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } + + break; + case 32: + if (videoRequest is not null) + { + videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } + + break; + case 33: + request.TranscodeReasons = val; + break; + } + } + } + + /// <summary> + /// Parses the container into its file extension. + /// </summary> + /// <param name="container">The container.</param> + private static string? GetContainerFileExtension(string? container) + { + if (string.Equals(container, "mpegts", StringComparison.OrdinalIgnoreCase)) + { + return "ts"; + } + + if (string.Equals(container, "matroska", StringComparison.OrdinalIgnoreCase)) + { + return "mkv"; + } + + return container; + } +} diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj new file mode 100644 index 00000000..3ccf7a74 --- /dev/null +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -0,0 +1,48 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <!-- ProjectGuid is only included as a requirement for SonarQube analysis --> + <PropertyGroup> + <ProjectGuid>{DFBEFB4C-DA19-4143-98B7-27320C7F7163}</ProjectGuid> + </PropertyGroup> + + <PropertyGroup> + <TargetFramework>net10.0</TargetFramework> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.AspNetCore.Authorization" /> + <PackageReference Include="Microsoft.Extensions.Http" /> + <PackageReference Include="Swashbuckle.AspNetCore" /> + <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> + <ProjectReference Include="..\MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj" /> + <ProjectReference Include="..\src\Jellyfin.MediaEncoding.Hls\Jellyfin.MediaEncoding.Hls.csproj" /> + <ProjectReference Include="..\src\Jellyfin.Networking\Jellyfin.Networking.csproj" /> + </ItemGroup> + + <!-- Code Analyzers --> + <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="IDisposableAnalyzers"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> + <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> + </ItemGroup> + + <ItemGroup> + <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> + <_Parameter1>Jellyfin.Api.Tests</_Parameter1> + </AssemblyAttribute> + </ItemGroup> + +</Project> diff --git a/Jellyfin.Api/Middleware/BaseUrlRedirectionMiddleware.cs b/Jellyfin.Api/Middleware/BaseUrlRedirectionMiddleware.cs new file mode 100644 index 00000000..cbd948db --- /dev/null +++ b/Jellyfin.Api/Middleware/BaseUrlRedirectionMiddleware.cs @@ -0,0 +1,78 @@ +using System; +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; + +namespace Jellyfin.Api.Middleware; + +/// <summary> +/// Redirect requests without baseurl prefix to the baseurl prefixed URL. +/// </summary> +public class BaseUrlRedirectionMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger<BaseUrlRedirectionMiddleware> _logger; + private readonly IConfiguration _configuration; + + /// <summary> + /// Initializes a new instance of the <see cref="BaseUrlRedirectionMiddleware"/> class. + /// </summary> + /// <param name="next">The next delegate in the pipeline.</param> + /// <param name="logger">The logger.</param> + /// <param name="configuration">The application configuration.</param> + public BaseUrlRedirectionMiddleware( + RequestDelegate next, + ILogger<BaseUrlRedirectionMiddleware> logger, + IConfiguration configuration) + { + _next = next; + _logger = logger; + _configuration = configuration; + } + + /// <summary> + /// Executes the middleware action. + /// </summary> + /// <param name="httpContext">The current HTTP context.</param> + /// <param name="serverConfigurationManager">The server configuration manager.</param> + /// <returns>The async task.</returns> + public async Task Invoke(HttpContext httpContext, IServerConfigurationManager serverConfigurationManager) + { + var localPath = httpContext.Request.Path.ToString(); + var baseUrlPrefix = serverConfigurationManager.GetNetworkConfiguration().BaseUrl; + + if (string.IsNullOrEmpty(localPath) + || string.Equals(localPath, baseUrlPrefix, StringComparison.OrdinalIgnoreCase) + || string.Equals(localPath, baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase) + || !localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase) + ) + { + // Redirect health endpoint + if (string.Equals(localPath, "/health", StringComparison.OrdinalIgnoreCase) + || string.Equals(localPath, "/health/", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogDebug("Redirecting /health check"); + httpContext.Response.Redirect(baseUrlPrefix + "/health"); + return; + } + + // Always redirect back to the default path if the base prefix is invalid or missing + _logger.LogDebug("Normalizing an URL at {LocalPath}", localPath); + + var port = httpContext.Request.Host.Port ?? -1; + var uri = new UriBuilder(httpContext.Request.Scheme, httpContext.Request.Host.Host, port, localPath).Uri; + var redirectUri = new UriBuilder(httpContext.Request.Scheme, httpContext.Request.Host.Host, port, baseUrlPrefix + "/" + _configuration[DefaultRedirectKey]).Uri; + var target = uri.MakeRelativeUri(redirectUri).ToString(); + _logger.LogDebug("Redirecting to {Target}", target); + + httpContext.Response.Redirect(target); + return; + } + + await _next(httpContext).ConfigureAwait(false); + } +} diff --git a/Jellyfin.Api/Middleware/ExceptionMiddleware.cs b/Jellyfin.Api/Middleware/ExceptionMiddleware.cs new file mode 100644 index 00000000..acbb4877 --- /dev/null +++ b/Jellyfin.Api/Middleware/ExceptionMiddleware.cs @@ -0,0 +1,150 @@ +using System; +using System.IO; +using System.Net.Mime; +using System.Net.Sockets; +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Middleware; + +/// <summary> +/// Exception Middleware. +/// </summary> +public class ExceptionMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger<ExceptionMiddleware> _logger; + private readonly IServerConfigurationManager _configuration; + private readonly IWebHostEnvironment _hostEnvironment; + + /// <summary> + /// Initializes a new instance of the <see cref="ExceptionMiddleware"/> class. + /// </summary> + /// <param name="next">Next request delegate.</param> + /// <param name="logger">Instance of the <see cref="ILogger{ExceptionMiddleware}"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="hostEnvironment">Instance of the <see cref="IWebHostEnvironment"/> interface.</param> + public ExceptionMiddleware( + RequestDelegate next, + ILogger<ExceptionMiddleware> logger, + IServerConfigurationManager serverConfigurationManager, + IWebHostEnvironment hostEnvironment) + { + _next = next; + _logger = logger; + _configuration = serverConfigurationManager; + _hostEnvironment = hostEnvironment; + } + + /// <summary> + /// Invoke request. + /// </summary> + /// <param name="context">Request context.</param> + /// <returns>Task.</returns> + public async Task Invoke(HttpContext context) + { + try + { + await _next(context).ConfigureAwait(false); + } + catch (Exception ex) + { + if (context.Response.HasStarted) + { + _logger.LogWarning("The response has already started, the exception middleware will not be executed."); + throw; + } + + ex = GetActualException(ex); + + bool ignoreStackTrace = + ex is SocketException + || ex is IOException + || ex is OperationCanceledException + || ex is SecurityException + || ex is AuthenticationException + || ex is FileNotFoundException; + + if (ignoreStackTrace) + { + _logger.LogError( + "Error processing request: {ExceptionMessage}. URL {Method} {Url}.", + ex.Message.TrimEnd('.'), + context.Request.Method, + context.Request.Path); + } + else + { + _logger.LogError( + ex, + "Error processing request. URL {Method} {Url}.", + context.Request.Method, + context.Request.Path); + } + + context.Response.StatusCode = GetStatusCode(ex); + context.Response.ContentType = MediaTypeNames.Text.Plain; + + // Don't send exception unless the server is in a Development environment + var errorContent = _hostEnvironment.IsDevelopment() + ? NormalizeExceptionMessage(ex.Message) + : "Error processing request."; + await context.Response.WriteAsync(errorContent).ConfigureAwait(false); + } + } + + private static Exception GetActualException(Exception ex) + { + if (ex is AggregateException agg) + { + var inner = agg.InnerException; + if (inner is not null) + { + return GetActualException(inner); + } + + var inners = agg.InnerExceptions; + if (inners.Count > 0) + { + return GetActualException(inners[0]); + } + } + + return ex; + } + + private static int GetStatusCode(Exception ex) + { + return ex switch + { + ArgumentException => StatusCodes.Status400BadRequest, + AuthenticationException => StatusCodes.Status401Unauthorized, + SecurityException => StatusCodes.Status403Forbidden, + DirectoryNotFoundException => StatusCodes.Status404NotFound, + FileNotFoundException => StatusCodes.Status404NotFound, + ResourceNotFoundException => StatusCodes.Status404NotFound, + MethodNotAllowedException => StatusCodes.Status405MethodNotAllowed, + _ => StatusCodes.Status500InternalServerError + }; + } + + private string NormalizeExceptionMessage(string msg) + { + // Strip any information we don't want to reveal + return msg.Replace( + _configuration.ApplicationPaths.ProgramSystemPath, + string.Empty, + StringComparison.OrdinalIgnoreCase) + .Replace( + _configuration.ApplicationPaths.ProgramDataPath, + string.Empty, + StringComparison.OrdinalIgnoreCase); + } +} diff --git a/Jellyfin.Api/Middleware/IpBasedAccessValidationMiddleware.cs b/Jellyfin.Api/Middleware/IpBasedAccessValidationMiddleware.cs new file mode 100644 index 00000000..a0ed6c81 --- /dev/null +++ b/Jellyfin.Api/Middleware/IpBasedAccessValidationMiddleware.cs @@ -0,0 +1,63 @@ +using System.Net; +using System.Threading.Tasks; +using System.Web; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Middleware; + +/// <summary> +/// Validates the IP of requests coming from local networks wrt. remote access. +/// </summary> +public class IPBasedAccessValidationMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger<IPBasedAccessValidationMiddleware> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="IPBasedAccessValidationMiddleware"/> class. + /// </summary> + /// <param name="next">The next delegate in the pipeline.</param> + /// <param name="logger">The logger to log to.</param> + public IPBasedAccessValidationMiddleware(RequestDelegate next, ILogger<IPBasedAccessValidationMiddleware> logger) + { + _next = next; + _logger = logger; + } + + /// <summary> + /// Executes the middleware action. + /// </summary> + /// <param name="httpContext">The current HTTP context.</param> + /// <param name="networkManager">The network manager.</param> + /// <returns>The async task.</returns> + public async Task Invoke(HttpContext httpContext, INetworkManager networkManager) + { + if (httpContext.IsLocal()) + { + // Accessing from the same machine as the server. + await _next(httpContext).ConfigureAwait(false); + return; + } + + var remoteIP = httpContext.GetNormalizedRemoteIP(); + + var result = networkManager.ShouldAllowServerAccess(remoteIP); + if (result != RemoteAccessPolicyResult.Allow) + { + // No access from network, respond with 503 instead of 200. + _logger.LogWarning( + "Blocking request to {Path} by {RemoteIP} due to IP filtering rule, reason: {Reason}", + // url-encode to block log injection + HttpUtility.UrlEncode(httpContext.Request.Path), + remoteIP, + result); + httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; + return; + } + + await _next(httpContext).ConfigureAwait(false); + } +} diff --git a/Jellyfin.Api/Middleware/QueryStringDecodingMiddleware.cs b/Jellyfin.Api/Middleware/QueryStringDecodingMiddleware.cs new file mode 100644 index 00000000..cb4169e9 --- /dev/null +++ b/Jellyfin.Api/Middleware/QueryStringDecodingMiddleware.cs @@ -0,0 +1,38 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; + +namespace Jellyfin.Api.Middleware; + +/// <summary> +/// URL decodes the querystring before binding. +/// </summary> +public class QueryStringDecodingMiddleware +{ + private readonly RequestDelegate _next; + + /// <summary> + /// Initializes a new instance of the <see cref="QueryStringDecodingMiddleware"/> class. + /// </summary> + /// <param name="next">The next delegate in the pipeline.</param> + public QueryStringDecodingMiddleware(RequestDelegate next) + { + _next = next; + } + + /// <summary> + /// Executes the middleware action. + /// </summary> + /// <param name="httpContext">The current HTTP context.</param> + /// <returns>The async task.</returns> + public async Task Invoke(HttpContext httpContext) + { + var feature = httpContext.Features.Get<IQueryFeature>(); + if (feature is not null) + { + httpContext.Features.Set<IQueryFeature>(new UrlDecodeQueryFeature(feature)); + } + + await _next(httpContext).ConfigureAwait(false); + } +} diff --git a/Jellyfin.Api/Middleware/ResponseTimeMiddleware.cs b/Jellyfin.Api/Middleware/ResponseTimeMiddleware.cs new file mode 100644 index 00000000..4a13d6c7 --- /dev/null +++ b/Jellyfin.Api/Middleware/ResponseTimeMiddleware.cs @@ -0,0 +1,68 @@ +using System.Diagnostics; +using System.Globalization; +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Middleware; + +/// <summary> +/// Response time middleware. +/// </summary> +public class ResponseTimeMiddleware +{ + private const string ResponseHeaderResponseTime = "X-Response-Time-ms"; + + private readonly RequestDelegate _next; + private readonly ILogger<ResponseTimeMiddleware> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="ResponseTimeMiddleware"/> class. + /// </summary> + /// <param name="next">Next request delegate.</param> + /// <param name="logger">Instance of the <see cref="ILogger{ExceptionMiddleware}"/> interface.</param> + public ResponseTimeMiddleware( + RequestDelegate next, + ILogger<ResponseTimeMiddleware> logger) + { + _next = next; + _logger = logger; + } + + /// <summary> + /// Invoke request. + /// </summary> + /// <param name="context">Request context.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <returns>Task.</returns> + public async Task Invoke(HttpContext context, IServerConfigurationManager serverConfigurationManager) + { + var startTimestamp = Stopwatch.GetTimestamp(); + + var enableWarning = serverConfigurationManager.Configuration.EnableSlowResponseWarning; + var warningThreshold = serverConfigurationManager.Configuration.SlowResponseThresholdMs; + context.Response.OnStarting(() => + { + var responseTime = Stopwatch.GetElapsedTime(startTimestamp); + var responseTimeMs = responseTime.TotalMilliseconds; + if (enableWarning && responseTimeMs > warningThreshold && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug( + "Slow HTTP Response from {Url} to {RemoteIP} in {Elapsed:g} with Status Code {StatusCode}", + context.Request.GetDisplayUrl(), + context.GetNormalizedRemoteIP(), + responseTime, + context.Response.StatusCode); + } + + context.Response.Headers[ResponseHeaderResponseTime] = responseTimeMs.ToString(CultureInfo.InvariantCulture); + return Task.CompletedTask; + }); + + // Call the next delegate/middleware in the pipeline + await this._next(context).ConfigureAwait(false); + } +} diff --git a/Jellyfin.Api/Middleware/RobotsRedirectionMiddleware.cs b/Jellyfin.Api/Middleware/RobotsRedirectionMiddleware.cs new file mode 100644 index 00000000..f716452d --- /dev/null +++ b/Jellyfin.Api/Middleware/RobotsRedirectionMiddleware.cs @@ -0,0 +1,45 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Middleware; + +/// <summary> +/// Redirect requests to robots.txt to web/robots.txt. +/// </summary> +public class RobotsRedirectionMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger<RobotsRedirectionMiddleware> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="RobotsRedirectionMiddleware"/> class. + /// </summary> + /// <param name="next">The next delegate in the pipeline.</param> + /// <param name="logger">The logger.</param> + public RobotsRedirectionMiddleware( + RequestDelegate next, + ILogger<RobotsRedirectionMiddleware> logger) + { + _next = next; + _logger = logger; + } + + /// <summary> + /// Executes the middleware action. + /// </summary> + /// <param name="httpContext">The current HTTP context.</param> + /// <returns>The async task.</returns> + public async Task Invoke(HttpContext httpContext) + { + if (httpContext.Request.Path.Equals("/robots.txt", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogDebug("Redirecting robots.txt request to web/robots.txt"); + httpContext.Response.Redirect("web/robots.txt"); + return; + } + + await _next(httpContext).ConfigureAwait(false); + } +} diff --git a/Jellyfin.Api/Middleware/ServerStartupMessageMiddleware.cs b/Jellyfin.Api/Middleware/ServerStartupMessageMiddleware.cs new file mode 100644 index 00000000..dcb23465 --- /dev/null +++ b/Jellyfin.Api/Middleware/ServerStartupMessageMiddleware.cs @@ -0,0 +1,50 @@ +using System; +using System.Net.Mime; +using System.Threading.Tasks; +using MediaBrowser.Controller; +using MediaBrowser.Model.Globalization; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Api.Middleware; + +/// <summary> +/// Shows a custom message during server startup. +/// </summary> +public class ServerStartupMessageMiddleware +{ + private readonly RequestDelegate _next; + + /// <summary> + /// Initializes a new instance of the <see cref="ServerStartupMessageMiddleware"/> class. + /// </summary> + /// <param name="next">The next delegate in the pipeline.</param> + public ServerStartupMessageMiddleware(RequestDelegate next) + { + _next = next; + } + + /// <summary> + /// Executes the middleware action. + /// </summary> + /// <param name="httpContext">The current HTTP context.</param> + /// <param name="serverApplicationHost">The server application host.</param> + /// <param name="localizationManager">The localization manager.</param> + /// <returns>The async task.</returns> + public async Task Invoke( + HttpContext httpContext, + IServerApplicationHost serverApplicationHost, + ILocalizationManager localizationManager) + { + if (serverApplicationHost.CoreStartupHasCompleted + || httpContext.Request.Path.Equals("/system/ping", StringComparison.OrdinalIgnoreCase)) + { + await _next(httpContext).ConfigureAwait(false); + return; + } + + var message = localizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); + httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; + httpContext.Response.ContentType = MediaTypeNames.Text.Html; + await httpContext.Response.WriteAsync(message, httpContext.RequestAborted).ConfigureAwait(false); + } +} diff --git a/Jellyfin.Api/Middleware/UrlDecodeQueryFeature.cs b/Jellyfin.Api/Middleware/UrlDecodeQueryFeature.cs new file mode 100644 index 00000000..f75d0d24 --- /dev/null +++ b/Jellyfin.Api/Middleware/UrlDecodeQueryFeature.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Jellyfin.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Primitives; + +namespace Jellyfin.Api.Middleware; + +/// <summary> +/// Defines the <see cref="UrlDecodeQueryFeature"/>. +/// </summary> +public class UrlDecodeQueryFeature : IQueryFeature +{ + private IQueryCollection? _store; + + /// <summary> + /// Initializes a new instance of the <see cref="UrlDecodeQueryFeature"/> class. + /// </summary> + /// <param name="feature">The <see cref="IQueryFeature"/> instance.</param> + public UrlDecodeQueryFeature(IQueryFeature feature) + { + Query = feature.Query; + } + + /// <summary> + /// Gets or sets a value indicating the url decoded <see cref="IQueryCollection"/>. + /// </summary> + public IQueryCollection Query + { + get + { + return _store ?? QueryCollection.Empty; + } + + set + { + // Only interested in where the querystring is encoded which shows up as one key with nothing in the value. + if (value.Count != 1) + { + _store = value; + return; + } + + // Encoded querystrings have no value, so don't process anything if a value is present. + var (key, stringValues) = value.First(); + if (!string.IsNullOrEmpty(stringValues)) + { + _store = value; + return; + } + + if (!key.Contains('=', StringComparison.Ordinal)) + { + _store = value; + return; + } + + var pairs = new Dictionary<string, StringValues>(); + foreach (var pair in key.SpanSplit('&')) + { + var i = pair.IndexOf('='); + if (i == -1) + { + // encoded is an equals. + // We use TryAdd so duplicate keys get ignored + pairs.TryAdd(pair.ToString(), StringValues.Empty); + continue; + } + + var k = pair[..i].ToString(); + var v = pair[(i + 1)..].ToString(); + if (!pairs.TryAdd(k, new StringValues(v))) + { + pairs[k] = StringValues.Concat(pairs[k], v); + } + } + + _store = new QueryCollection(pairs); + } + } +} diff --git a/Jellyfin.Api/Middleware/WebSocketHandlerMiddleware.cs b/Jellyfin.Api/Middleware/WebSocketHandlerMiddleware.cs new file mode 100644 index 00000000..009fb626 --- /dev/null +++ b/Jellyfin.Api/Middleware/WebSocketHandlerMiddleware.cs @@ -0,0 +1,39 @@ +using System.Threading.Tasks; +using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Api.Middleware; + +/// <summary> +/// Handles WebSocket requests. +/// </summary> +public class WebSocketHandlerMiddleware +{ + private readonly RequestDelegate _next; + + /// <summary> + /// Initializes a new instance of the <see cref="WebSocketHandlerMiddleware"/> class. + /// </summary> + /// <param name="next">The next delegate in the pipeline.</param> + public WebSocketHandlerMiddleware(RequestDelegate next) + { + _next = next; + } + + /// <summary> + /// Executes the middleware action. + /// </summary> + /// <param name="httpContext">The current HTTP context.</param> + /// <param name="webSocketManager">The WebSocket connection manager.</param> + /// <returns>The async task.</returns> + public async Task Invoke(HttpContext httpContext, IWebSocketManager webSocketManager) + { + if (!httpContext.WebSockets.IsWebSocketRequest) + { + await _next(httpContext).ConfigureAwait(false); + return; + } + + await webSocketManager.WebSocketRequestHandler(httpContext).ConfigureAwait(false); + } +} diff --git a/Jellyfin.Api/ModelBinders/CommaDelimitedCollectionModelBinder.cs b/Jellyfin.Api/ModelBinders/CommaDelimitedCollectionModelBinder.cs new file mode 100644 index 00000000..25b84cbc --- /dev/null +++ b/Jellyfin.Api/ModelBinders/CommaDelimitedCollectionModelBinder.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.ModelBinders; + +/// <summary> +/// Comma delimited collection model binder. +/// Returns an empty array of specified type if there is no query parameter. +/// </summary> +public class CommaDelimitedCollectionModelBinder : IModelBinder +{ + private readonly ILogger<CommaDelimitedCollectionModelBinder> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="CommaDelimitedCollectionModelBinder"/> class. + /// </summary> + /// <param name="logger">Instance of the <see cref="ILogger{CommaDelimitedCollectionModelBinder}"/> interface.</param> + public CommaDelimitedCollectionModelBinder(ILogger<CommaDelimitedCollectionModelBinder> logger) + { + _logger = logger; + } + + /// <inheritdoc/> + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + var elementType = bindingContext.ModelType.GetElementType() ?? bindingContext.ModelType.GenericTypeArguments[0]; + var converter = TypeDescriptor.GetConverter(elementType); + + if (valueProviderResult.Length > 1) + { + var typedValues = GetParsedResult(valueProviderResult.Values, elementType, converter); + bindingContext.Result = ModelBindingResult.Success(typedValues); + } + else + { + var value = valueProviderResult.FirstValue; + + if (value is not null) + { + var splitValues = value.Split(',', StringSplitOptions.RemoveEmptyEntries); + var typedValues = GetParsedResult(splitValues, elementType, converter); + bindingContext.Result = ModelBindingResult.Success(typedValues); + } + else + { + var emptyResult = Array.CreateInstance(elementType, 0); + bindingContext.Result = ModelBindingResult.Success(emptyResult); + } + } + + return Task.CompletedTask; + } + + private Array GetParsedResult(IReadOnlyList<string> values, Type elementType, TypeConverter converter) + { + var parsedValues = new object?[values.Count]; + var convertedCount = 0; + for (var i = 0; i < values.Count; i++) + { + try + { + parsedValues[i] = converter.ConvertFromString(values[i].Trim()); + convertedCount++; + } + catch (FormatException e) + { + _logger.LogDebug(e, "Error converting value."); + } + } + + var typedValues = Array.CreateInstance(elementType, convertedCount); + var typedValueIndex = 0; + for (var i = 0; i < parsedValues.Length; i++) + { + if (parsedValues[i] is not null) + { + typedValues.SetValue(parsedValues[i], typedValueIndex); + typedValueIndex++; + } + } + + return typedValues; + } +} diff --git a/Jellyfin.Api/ModelBinders/LegacyDateTimeModelBinder.cs b/Jellyfin.Api/ModelBinders/LegacyDateTimeModelBinder.cs new file mode 100644 index 00000000..1ecf89b2 --- /dev/null +++ b/Jellyfin.Api/ModelBinders/LegacyDateTimeModelBinder.cs @@ -0,0 +1,48 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.ModelBinders; + +/// <summary> +/// DateTime model binder. +/// </summary> +public class LegacyDateTimeModelBinder : IModelBinder +{ + // Borrowed from the DateTimeModelBinderProvider + private const DateTimeStyles SupportedStyles = DateTimeStyles.AdjustToUniversal | DateTimeStyles.AllowWhiteSpaces; + private readonly DateTimeModelBinder _defaultModelBinder; + + /// <summary> + /// Initializes a new instance of the <see cref="LegacyDateTimeModelBinder"/> class. + /// </summary> + /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param> + public LegacyDateTimeModelBinder(ILoggerFactory loggerFactory) + { + _defaultModelBinder = new DateTimeModelBinder(SupportedStyles, loggerFactory); + } + + /// <inheritdoc /> + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + if (valueProviderResult.Values.Count == 1) + { + var dateTimeString = valueProviderResult.FirstValue; + // Mark Played Item. + if (DateTime.TryParseExact(dateTimeString, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var dateTime)) + { + bindingContext.Result = ModelBindingResult.Success(dateTime); + } + else + { + return _defaultModelBinder.BindModelAsync(bindingContext); + } + } + + return Task.CompletedTask; + } +} diff --git a/Jellyfin.Api/ModelBinders/NullableEnumModelBinder.cs b/Jellyfin.Api/ModelBinders/NullableEnumModelBinder.cs new file mode 100644 index 00000000..79a3e135 --- /dev/null +++ b/Jellyfin.Api/ModelBinders/NullableEnumModelBinder.cs @@ -0,0 +1,47 @@ +using System; +using System.ComponentModel; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.ModelBinders; + +/// <summary> +/// Nullable enum model binder. +/// </summary> +public class NullableEnumModelBinder : IModelBinder +{ + private readonly ILogger<NullableEnumModelBinder> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="NullableEnumModelBinder"/> class. + /// </summary> + /// <param name="logger">Instance of the <see cref="ILogger{NullableEnumModelBinder}"/> interface.</param> + public NullableEnumModelBinder(ILogger<NullableEnumModelBinder> logger) + { + _logger = logger; + } + + /// <inheritdoc /> + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + var elementType = bindingContext.ModelType.GetElementType() ?? bindingContext.ModelType.GenericTypeArguments[0]; + var converter = TypeDescriptor.GetConverter(elementType); + if (valueProviderResult.Length != 0) + { + try + { + // REVIEW: This shouldn't be null here + var convertedValue = converter.ConvertFromString(valueProviderResult.FirstValue!); + bindingContext.Result = ModelBindingResult.Success(convertedValue); + } + catch (FormatException e) + { + _logger.LogDebug(e, "Error converting value."); + } + } + + return Task.CompletedTask; + } +} diff --git a/Jellyfin.Api/ModelBinders/NullableEnumModelBinderProvider.cs b/Jellyfin.Api/ModelBinders/NullableEnumModelBinderProvider.cs new file mode 100644 index 00000000..d4ec04ee --- /dev/null +++ b/Jellyfin.Api/ModelBinders/NullableEnumModelBinderProvider.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.ModelBinders; + +/// <summary> +/// Nullable enum model binder provider. +/// </summary> +public class NullableEnumModelBinderProvider : IModelBinderProvider +{ + /// <inheritdoc /> + public IModelBinder? GetBinder(ModelBinderProviderContext context) + { + var nullableType = Nullable.GetUnderlyingType(context.Metadata.ModelType); + if (nullableType is null || !nullableType.IsEnum) + { + // Type isn't nullable or isn't an enum. + return null; + } + + var logger = context.Services.GetRequiredService<ILogger<NullableEnumModelBinder>>(); + return new NullableEnumModelBinder(logger); + } +} diff --git a/Jellyfin.Api/ModelBinders/PipeDelimitedCollectionModelBinder.cs b/Jellyfin.Api/ModelBinders/PipeDelimitedCollectionModelBinder.cs new file mode 100644 index 00000000..7d0fb2e1 --- /dev/null +++ b/Jellyfin.Api/ModelBinders/PipeDelimitedCollectionModelBinder.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.ModelBinders; + +/// <summary> +/// Comma delimited collection model binder. +/// Returns an empty collection of specified type if there is no query parameter. +/// </summary> +public class PipeDelimitedCollectionModelBinder : IModelBinder +{ + private readonly ILogger<PipeDelimitedCollectionModelBinder> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="PipeDelimitedCollectionModelBinder"/> class. + /// </summary> + /// <param name="logger">Instance of the <see cref="ILogger{PipeDelimitedCollectionModelBinder}"/> interface.</param> + public PipeDelimitedCollectionModelBinder(ILogger<PipeDelimitedCollectionModelBinder> logger) + { + _logger = logger; + } + + /// <inheritdoc/> + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + var elementType = bindingContext.ModelType.GetElementType() ?? bindingContext.ModelType.GenericTypeArguments[0]; + var converter = TypeDescriptor.GetConverter(elementType); + + if (valueProviderResult.Length > 1) + { + var typedValues = GetParsedResult(valueProviderResult.Values, elementType, converter); + bindingContext.Result = ModelBindingResult.Success(typedValues); + } + else + { + var value = valueProviderResult.FirstValue; + + if (value is not null) + { + var splitValues = value.Split('|', StringSplitOptions.RemoveEmptyEntries); + var typedValues = GetParsedResult(splitValues, elementType, converter); + bindingContext.Result = ModelBindingResult.Success(typedValues); + } + else + { + var emptyResult = Array.CreateInstance(elementType, 0); + bindingContext.Result = ModelBindingResult.Success(emptyResult); + } + } + + return Task.CompletedTask; + } + + private Array GetParsedResult(IReadOnlyList<string> values, Type elementType, TypeConverter converter) + { + var parsedValues = new object?[values.Count]; + var convertedCount = 0; + for (var i = 0; i < values.Count; i++) + { + try + { + parsedValues[i] = converter.ConvertFromString(values[i].Trim()); + convertedCount++; + } + catch (FormatException e) + { + _logger.LogDebug(e, "Error converting value."); + } + } + + var typedValues = Array.CreateInstance(elementType, convertedCount); + var typedValueIndex = 0; + for (var i = 0; i < parsedValues.Length; i++) + { + if (parsedValues[i] is not null) + { + typedValues.SetValue(parsedValues[i], typedValueIndex); + typedValueIndex++; + } + } + + return typedValues; + } +} diff --git a/Jellyfin.Api/Models/ClientLogDtos/ClientLogDocumentResponseDto.cs b/Jellyfin.Api/Models/ClientLogDtos/ClientLogDocumentResponseDto.cs new file mode 100644 index 00000000..168247fd --- /dev/null +++ b/Jellyfin.Api/Models/ClientLogDtos/ClientLogDocumentResponseDto.cs @@ -0,0 +1,21 @@ +namespace Jellyfin.Api.Models.ClientLogDtos; + +/// <summary> +/// Client log document response dto. +/// </summary> +public class ClientLogDocumentResponseDto +{ + /// <summary> + /// Initializes a new instance of the <see cref="ClientLogDocumentResponseDto"/> class. + /// </summary> + /// <param name="fileName">The file name.</param> + public ClientLogDocumentResponseDto(string fileName) + { + FileName = fileName; + } + + /// <summary> + /// Gets the resulting filename. + /// </summary> + public string FileName { get; } +} diff --git a/Jellyfin.Api/Models/ConfigurationPageInfo.cs b/Jellyfin.Api/Models/ConfigurationPageInfo.cs new file mode 100644 index 00000000..e7bcd6c5 --- /dev/null +++ b/Jellyfin.Api/Models/ConfigurationPageInfo.cs @@ -0,0 +1,66 @@ +using System; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Plugins; + +namespace Jellyfin.Api.Models; + +/// <summary> +/// The configuration page info. +/// </summary> +public class ConfigurationPageInfo +{ + /// <summary> + /// Initializes a new instance of the <see cref="ConfigurationPageInfo"/> class. + /// </summary> + /// <param name="plugin">Instance of <see cref="IPlugin"/> interface.</param> + /// <param name="page">Instance of <see cref="PluginPageInfo"/> interface.</param> + public ConfigurationPageInfo(IPlugin? plugin, PluginPageInfo page) + { + Name = page.Name; + EnableInMainMenu = page.EnableInMainMenu; + MenuSection = page.MenuSection; + MenuIcon = page.MenuIcon; + DisplayName = string.IsNullOrWhiteSpace(page.DisplayName) ? plugin?.Name : page.DisplayName; + PluginId = plugin?.Id; + } + + /// <summary> + /// Initializes a new instance of the <see cref="ConfigurationPageInfo"/> class. + /// </summary> + public ConfigurationPageInfo() + { + Name = string.Empty; + } + + /// <summary> + /// Gets or sets the name. + /// </summary> + /// <value>The name.</value> + public string Name { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the configurations page is enabled in the main menu. + /// </summary> + public bool EnableInMainMenu { get; set; } + + /// <summary> + /// Gets or sets the menu section. + /// </summary> + public string? MenuSection { get; set; } + + /// <summary> + /// Gets or sets the menu icon. + /// </summary> + public string? MenuIcon { get; set; } + + /// <summary> + /// Gets or sets the display name. + /// </summary> + public string? DisplayName { get; set; } + + /// <summary> + /// Gets or sets the plugin id. + /// </summary> + /// <value>The plugin id.</value> + public Guid? PluginId { get; set; } +} diff --git a/Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfoDto.cs b/Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfoDto.cs new file mode 100644 index 00000000..c438e5a9 --- /dev/null +++ b/Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfoDto.cs @@ -0,0 +1,12 @@ +namespace Jellyfin.Api.Models.EnvironmentDtos; + +/// <summary> +/// Default directory browser info. +/// </summary> +public class DefaultDirectoryBrowserInfoDto +{ + /// <summary> + /// Gets or sets the path. + /// </summary> + public string? Path { get; set; } +} diff --git a/Jellyfin.Api/Models/EnvironmentDtos/ValidatePathDto.cs b/Jellyfin.Api/Models/EnvironmentDtos/ValidatePathDto.cs new file mode 100644 index 00000000..c54205bf --- /dev/null +++ b/Jellyfin.Api/Models/EnvironmentDtos/ValidatePathDto.cs @@ -0,0 +1,22 @@ +namespace Jellyfin.Api.Models.EnvironmentDtos; + +/// <summary> +/// Validate path object. +/// </summary> +public class ValidatePathDto +{ + /// <summary> + /// Gets or sets a value indicating whether validate if path is writable. + /// </summary> + public bool ValidateWritable { get; set; } + + /// <summary> + /// Gets or sets the path. + /// </summary> + public string? Path { get; set; } + + /// <summary> + /// Gets or sets is path file. + /// </summary> + public bool? IsFile { get; set; } +} diff --git a/Jellyfin.Api/Models/LibraryDtos/LibraryOptionInfoDto.cs b/Jellyfin.Api/Models/LibraryDtos/LibraryOptionInfoDto.cs new file mode 100644 index 00000000..3af6f532 --- /dev/null +++ b/Jellyfin.Api/Models/LibraryDtos/LibraryOptionInfoDto.cs @@ -0,0 +1,17 @@ +namespace Jellyfin.Api.Models.LibraryDtos; + +/// <summary> +/// Library option info dto. +/// </summary> +public class LibraryOptionInfoDto +{ + /// <summary> + /// Gets or sets name. + /// </summary> + public string? Name { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether default enabled. + /// </summary> + public bool DefaultEnabled { get; set; } +} diff --git a/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs b/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs new file mode 100644 index 00000000..c4924366 --- /dev/null +++ b/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + +namespace Jellyfin.Api.Models.LibraryDtos; + +/// <summary> +/// Library options result dto. +/// </summary> +public class LibraryOptionsResultDto +{ + /// <summary> + /// Gets or sets the metadata savers. + /// </summary> + public IReadOnlyList<LibraryOptionInfoDto> MetadataSavers { get; set; } = Array.Empty<LibraryOptionInfoDto>(); + + /// <summary> + /// Gets or sets the metadata readers. + /// </summary> + public IReadOnlyList<LibraryOptionInfoDto> MetadataReaders { get; set; } = Array.Empty<LibraryOptionInfoDto>(); + + /// <summary> + /// Gets or sets the subtitle fetchers. + /// </summary> + public IReadOnlyList<LibraryOptionInfoDto> SubtitleFetchers { get; set; } = Array.Empty<LibraryOptionInfoDto>(); + + /// <summary> + /// Gets or sets the list of lyric fetchers. + /// </summary> + public IReadOnlyList<LibraryOptionInfoDto> LyricFetchers { get; set; } = Array.Empty<LibraryOptionInfoDto>(); + + /// <summary> + /// Gets or sets the list of MediaSegment Providers. + /// </summary> + public IReadOnlyList<LibraryOptionInfoDto> MediaSegmentProviders { get; set; } = Array.Empty<LibraryOptionInfoDto>(); + + /// <summary> + /// Gets or sets the type options. + /// </summary> + public IReadOnlyList<LibraryTypeOptionsDto> TypeOptions { get; set; } = Array.Empty<LibraryTypeOptionsDto>(); +} diff --git a/Jellyfin.Api/Models/LibraryDtos/LibraryTypeOptionsDto.cs b/Jellyfin.Api/Models/LibraryDtos/LibraryTypeOptionsDto.cs new file mode 100644 index 00000000..f76c4a96 --- /dev/null +++ b/Jellyfin.Api/Models/LibraryDtos/LibraryTypeOptionsDto.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Entities; + +namespace Jellyfin.Api.Models.LibraryDtos; + +/// <summary> +/// Library type options dto. +/// </summary> +public class LibraryTypeOptionsDto +{ + /// <summary> + /// Gets or sets the type. + /// </summary> + public string? Type { get; set; } + + /// <summary> + /// Gets or sets the metadata fetchers. + /// </summary> + public IReadOnlyList<LibraryOptionInfoDto> MetadataFetchers { get; set; } = Array.Empty<LibraryOptionInfoDto>(); + + /// <summary> + /// Gets or sets the image fetchers. + /// </summary> + public IReadOnlyList<LibraryOptionInfoDto> ImageFetchers { get; set; } = Array.Empty<LibraryOptionInfoDto>(); + + /// <summary> + /// Gets or sets the supported image types. + /// </summary> + public IReadOnlyList<ImageType> SupportedImageTypes { get; set; } = Array.Empty<ImageType>(); + + /// <summary> + /// Gets or sets the default image options. + /// </summary> + public IReadOnlyList<ImageOption> DefaultImageOptions { get; set; } = Array.Empty<ImageOption>(); +} diff --git a/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs new file mode 100644 index 00000000..7b7e9dfd --- /dev/null +++ b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace Jellyfin.Api.Models.LibraryDtos; + +/// <summary> +/// Media Update Info Dto. +/// </summary> +public class MediaUpdateInfoDto +{ + /// <summary> + /// Gets or sets the list of updates. + /// </summary> + public IReadOnlyList<MediaUpdateInfoPathDto> Updates { get; set; } = Array.Empty<MediaUpdateInfoPathDto>(); +} diff --git a/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs new file mode 100644 index 00000000..dbcc73c6 --- /dev/null +++ b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs @@ -0,0 +1,18 @@ +namespace Jellyfin.Api.Models.LibraryDtos; + +/// <summary> +/// The media update info path. +/// </summary> +public class MediaUpdateInfoPathDto +{ + /// <summary> + /// Gets or sets media path. + /// </summary> + public string? Path { get; set; } + + /// <summary> + /// Gets or sets media update type. + /// Created, Modified, Deleted. + /// </summary> + public string? UpdateType { get; set; } +} diff --git a/Jellyfin.Api/Models/LibraryStructureDto/AddVirtualFolderDto.cs b/Jellyfin.Api/Models/LibraryStructureDto/AddVirtualFolderDto.cs new file mode 100644 index 00000000..0066d1e3 --- /dev/null +++ b/Jellyfin.Api/Models/LibraryStructureDto/AddVirtualFolderDto.cs @@ -0,0 +1,14 @@ +using MediaBrowser.Model.Configuration; + +namespace Jellyfin.Api.Models.LibraryStructureDto; + +/// <summary> +/// Add virtual folder dto. +/// </summary> +public class AddVirtualFolderDto +{ + /// <summary> + /// Gets or sets library options. + /// </summary> + public LibraryOptions? LibraryOptions { get; set; } +} diff --git a/Jellyfin.Api/Models/LibraryStructureDto/MediaPathDto.cs b/Jellyfin.Api/Models/LibraryStructureDto/MediaPathDto.cs new file mode 100644 index 00000000..8a313d59 --- /dev/null +++ b/Jellyfin.Api/Models/LibraryStructureDto/MediaPathDto.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; +using MediaBrowser.Model.Configuration; + +namespace Jellyfin.Api.Models.LibraryStructureDto; + +/// <summary> +/// Media Path dto. +/// </summary> +public class MediaPathDto +{ + /// <summary> + /// Gets or sets the name of the library. + /// </summary> + [Required] + public required string Name { get; set; } + + /// <summary> + /// Gets or sets the path to add. + /// </summary> + public string? Path { get; set; } + + /// <summary> + /// Gets or sets the path info. + /// </summary> + public MediaPathInfo? PathInfo { get; set; } +} diff --git a/Jellyfin.Api/Models/LibraryStructureDto/UpdateLibraryOptionsDto.cs b/Jellyfin.Api/Models/LibraryStructureDto/UpdateLibraryOptionsDto.cs new file mode 100644 index 00000000..2dbd3025 --- /dev/null +++ b/Jellyfin.Api/Models/LibraryStructureDto/UpdateLibraryOptionsDto.cs @@ -0,0 +1,20 @@ +using System; +using MediaBrowser.Model.Configuration; + +namespace Jellyfin.Api.Models.LibraryStructureDto; + +/// <summary> +/// Update library options dto. +/// </summary> +public class UpdateLibraryOptionsDto +{ + /// <summary> + /// Gets or sets the library item id. + /// </summary> + public Guid Id { get; set; } + + /// <summary> + /// Gets or sets library options. + /// </summary> + public LibraryOptions? LibraryOptions { get; set; } +} diff --git a/Jellyfin.Api/Models/LibraryStructureDto/UpdateMediaPathRequestDto.cs b/Jellyfin.Api/Models/LibraryStructureDto/UpdateMediaPathRequestDto.cs new file mode 100644 index 00000000..85935b94 --- /dev/null +++ b/Jellyfin.Api/Models/LibraryStructureDto/UpdateMediaPathRequestDto.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.DataAnnotations; +using MediaBrowser.Model.Configuration; + +namespace Jellyfin.Api.Models.LibraryStructureDto; + +/// <summary> +/// Update library options dto. +/// </summary> +public class UpdateMediaPathRequestDto +{ + /// <summary> + /// Gets or sets the library name. + /// </summary> + [Required] + public string Name { get; set; } = null!; + + /// <summary> + /// Gets or sets library folder path information. + /// </summary> + [Required] + public MediaPathInfo PathInfo { get; set; } = null!; +} diff --git a/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs new file mode 100644 index 00000000..2616694d --- /dev/null +++ b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions.Json.Converters; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; + +namespace Jellyfin.Api.Models.LiveTvDtos; + +/// <summary> +/// Get programs dto. +/// </summary> +public class GetProgramsDto +{ + /// <summary> + /// Gets or sets the channels to return guide information for. + /// </summary> + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] + public IReadOnlyList<Guid>? ChannelIds { get; set; } + + /// <summary> + /// Gets or sets optional. Filter by user id. + /// </summary> + public Guid? UserId { get; set; } + + /// <summary> + /// Gets or sets the minimum premiere start date. + /// </summary> + public DateTime? MinStartDate { get; set; } + + /// <summary> + /// Gets or sets filter by programs that have completed airing, or not. + /// </summary> + public bool? HasAired { get; set; } + + /// <summary> + /// Gets or sets filter by programs that are currently airing, or not. + /// </summary> + public bool? IsAiring { get; set; } + + /// <summary> + /// Gets or sets the maximum premiere start date. + /// </summary> + public DateTime? MaxStartDate { get; set; } + + /// <summary> + /// Gets or sets the minimum premiere end date. + /// </summary> + public DateTime? MinEndDate { get; set; } + + /// <summary> + /// Gets or sets the maximum premiere end date. + /// </summary> + public DateTime? MaxEndDate { get; set; } + + /// <summary> + /// Gets or sets filter for movies. + /// </summary> + public bool? IsMovie { get; set; } + + /// <summary> + /// Gets or sets filter for series. + /// </summary> + public bool? IsSeries { get; set; } + + /// <summary> + /// Gets or sets filter for news. + /// </summary> + public bool? IsNews { get; set; } + + /// <summary> + /// Gets or sets filter for kids. + /// </summary> + public bool? IsKids { get; set; } + + /// <summary> + /// Gets or sets filter for sports. + /// </summary> + public bool? IsSports { get; set; } + + /// <summary> + /// Gets or sets the record index to start at. All items with a lower index will be dropped from the results. + /// </summary> + public int? StartIndex { get; set; } + + /// <summary> + /// Gets or sets the maximum number of records to return. + /// </summary> + public int? Limit { get; set; } + + /// <summary> + /// Gets or sets specify one or more sort orders, comma delimited. Options: Name, StartDate. + /// </summary> + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] + public IReadOnlyList<ItemSortBy>? SortBy { get; set; } + + /// <summary> + /// Gets or sets sort order. + /// </summary> + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] + public IReadOnlyList<SortOrder>? SortOrder { get; set; } + + /// <summary> + /// Gets or sets the genres to return guide information for. + /// </summary> + [JsonConverter(typeof(JsonPipeDelimitedCollectionConverterFactory))] + public IReadOnlyList<string>? Genres { get; set; } + + /// <summary> + /// Gets or sets the genre ids to return guide information for. + /// </summary> + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] + public IReadOnlyList<Guid>? GenreIds { get; set; } + + /// <summary> + /// Gets or sets include image information in output. + /// </summary> + public bool? EnableImages { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether retrieve total record count. + /// </summary> + [DefaultValue(true)] + public bool EnableTotalRecordCount { get; set; } = true; + + /// <summary> + /// Gets or sets the max number of images to return, per image type. + /// </summary> + public int? ImageTypeLimit { get; set; } + + /// <summary> + /// Gets or sets the image types to include in the output. + /// </summary> + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] + public IReadOnlyList<ImageType>? EnableImageTypes { get; set; } + + /// <summary> + /// Gets or sets include user data. + /// </summary> + public bool? EnableUserData { get; set; } + + /// <summary> + /// Gets or sets filter by series timer id. + /// </summary> + public string? SeriesTimerId { get; set; } + + /// <summary> + /// Gets or sets filter by library series id. + /// </summary> + public Guid? LibrarySeriesId { get; set; } + + /// <summary> + /// Gets or sets specify additional fields of information to return in the output. + /// </summary> + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] + public IReadOnlyList<ItemFields>? Fields { get; set; } +} diff --git a/Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs b/Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs new file mode 100644 index 00000000..1acabb76 --- /dev/null +++ b/Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; + +namespace Jellyfin.Api.Models.LiveTvDtos; + +/// <summary> +/// Set channel mapping dto. +/// </summary> +public class SetChannelMappingDto +{ + /// <summary> + /// Gets or sets the provider id. + /// </summary> + [Required] + public string ProviderId { get; set; } = string.Empty; + + /// <summary> + /// Gets or sets the tuner channel id. + /// </summary> + [Required] + public string TunerChannelId { get; set; } = string.Empty; + + /// <summary> + /// Gets or sets the provider channel id. + /// </summary> + [Required] + public string ProviderChannelId { get; set; } = string.Empty; +} diff --git a/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs b/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs new file mode 100644 index 00000000..758c8993 --- /dev/null +++ b/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.MediaInfo; + +namespace Jellyfin.Api.Models.MediaInfoDtos; + +/// <summary> +/// Open live stream dto. +/// </summary> +public class OpenLiveStreamDto +{ + /// <summary> + /// Gets or sets the open token. + /// </summary> + public string? OpenToken { get; set; } + + /// <summary> + /// Gets or sets the user id. + /// </summary> + public Guid? UserId { get; set; } + + /// <summary> + /// Gets or sets the play session id. + /// </summary> + public string? PlaySessionId { get; set; } + + /// <summary> + /// Gets or sets the max streaming bitrate. + /// </summary> + public int? MaxStreamingBitrate { get; set; } + + /// <summary> + /// Gets or sets the start time in ticks. + /// </summary> + public long? StartTimeTicks { get; set; } + + /// <summary> + /// Gets or sets the audio stream index. + /// </summary> + public int? AudioStreamIndex { get; set; } + + /// <summary> + /// Gets or sets the subtitle stream index. + /// </summary> + public int? SubtitleStreamIndex { get; set; } + + /// <summary> + /// Gets or sets the max audio channels. + /// </summary> + public int? MaxAudioChannels { get; set; } + + /// <summary> + /// Gets or sets the item id. + /// </summary> + public Guid? ItemId { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to enable direct play. + /// </summary> + public bool? EnableDirectPlay { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to enable direct stream. + /// </summary> + public bool? EnableDirectStream { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether always burn in subtitles when transcoding. + /// </summary> + public bool? AlwaysBurnInSubtitleWhenTranscoding { get; set; } + + /// <summary> + /// Gets or sets the device profile. + /// </summary> + public DeviceProfile? DeviceProfile { get; set; } + + /// <summary> + /// Gets or sets the device play protocols. + /// </summary> + public IReadOnlyList<MediaProtocol> DirectPlayProtocols { get; set; } = Array.Empty<MediaProtocol>(); +} diff --git a/Jellyfin.Api/Models/MediaInfoDtos/PlaybackInfoDto.cs b/Jellyfin.Api/Models/MediaInfoDtos/PlaybackInfoDto.cs new file mode 100644 index 00000000..73ab7681 --- /dev/null +++ b/Jellyfin.Api/Models/MediaInfoDtos/PlaybackInfoDto.cs @@ -0,0 +1,90 @@ +using System; +using MediaBrowser.Model.Dlna; + +namespace Jellyfin.Api.Models.MediaInfoDtos; + +/// <summary> +/// Playback info dto. +/// </summary> +public class PlaybackInfoDto +{ + /// <summary> + /// Gets or sets the playback userId. + /// </summary> + public Guid? UserId { get; set; } + + /// <summary> + /// Gets or sets the max streaming bitrate. + /// </summary> + public int? MaxStreamingBitrate { get; set; } + + /// <summary> + /// Gets or sets the start time in ticks. + /// </summary> + public long? StartTimeTicks { get; set; } + + /// <summary> + /// Gets or sets the audio stream index. + /// </summary> + public int? AudioStreamIndex { get; set; } + + /// <summary> + /// Gets or sets the subtitle stream index. + /// </summary> + public int? SubtitleStreamIndex { get; set; } + + /// <summary> + /// Gets or sets the max audio channels. + /// </summary> + public int? MaxAudioChannels { get; set; } + + /// <summary> + /// Gets or sets the media source id. + /// </summary> + public string? MediaSourceId { get; set; } + + /// <summary> + /// Gets or sets the live stream id. + /// </summary> + public string? LiveStreamId { get; set; } + + /// <summary> + /// Gets or sets the device profile. + /// </summary> + public DeviceProfile? DeviceProfile { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to enable direct play. + /// </summary> + public bool? EnableDirectPlay { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to enable direct stream. + /// </summary> + public bool? EnableDirectStream { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to enable transcoding. + /// </summary> + public bool? EnableTranscoding { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to enable video stream copy. + /// </summary> + public bool? AllowVideoStreamCopy { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to allow audio stream copy. + /// </summary> + public bool? AllowAudioStreamCopy { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to auto open the live stream. + /// </summary> + public bool? AutoOpenLiveStream { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether always burn in subtitles when transcoding. + /// </summary> + public bool? AlwaysBurnInSubtitleWhenTranscoding { get; set; } +} diff --git a/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs b/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs new file mode 100644 index 00000000..891d758c --- /dev/null +++ b/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions.Json.Converters; +using MediaBrowser.Model.Entities; + +namespace Jellyfin.Api.Models.PlaylistDtos; + +/// <summary> +/// Create new playlist dto. +/// </summary> +public class CreatePlaylistDto +{ + /// <summary> + /// Gets or sets the name of the new playlist. + /// </summary> + public required string Name { get; set; } + + /// <summary> + /// Gets or sets item ids to add to the playlist. + /// </summary> + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] + public IReadOnlyList<Guid> Ids { get; set; } = []; + + /// <summary> + /// Gets or sets the user id. + /// </summary> + public Guid? UserId { get; set; } + + /// <summary> + /// Gets or sets the media type. + /// </summary> + public MediaType? MediaType { get; set; } + + /// <summary> + /// Gets or sets the playlist users. + /// </summary> + public IReadOnlyList<PlaylistUserPermissions> Users { get; set; } = []; + + /// <summary> + /// Gets or sets a value indicating whether the playlist is public. + /// </summary> + public bool IsPublic { get; set; } = true; +} diff --git a/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistDto.cs b/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistDto.cs new file mode 100644 index 00000000..339a0d5d --- /dev/null +++ b/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistDto.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Jellyfin.Extensions.Json.Converters; +using MediaBrowser.Model.Entities; + +namespace Jellyfin.Api.Models.PlaylistDtos; + +/// <summary> +/// Update existing playlist dto. Fields set to `null` will not be updated and keep their current values. +/// </summary> +public class UpdatePlaylistDto +{ + /// <summary> + /// Gets or sets the name of the new playlist. + /// </summary> + public string? Name { get; set; } + + /// <summary> + /// Gets or sets item ids of the playlist. + /// </summary> + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] + public IReadOnlyList<Guid>? Ids { get; set; } + + /// <summary> + /// Gets or sets the playlist users. + /// </summary> + public IReadOnlyList<PlaylistUserPermissions>? Users { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the playlist is public. + /// </summary> + public bool? IsPublic { get; set; } +} diff --git a/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistUserDto.cs b/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistUserDto.cs new file mode 100644 index 00000000..60467b5e --- /dev/null +++ b/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistUserDto.cs @@ -0,0 +1,12 @@ +namespace Jellyfin.Api.Models.PlaylistDtos; + +/// <summary> +/// Update existing playlist user dto. Fields set to `null` will not be updated and keep their current values. +/// </summary> +public class UpdatePlaylistUserDto +{ + /// <summary> + /// Gets or sets a value indicating whether the user can edit the playlist. + /// </summary> + public bool? CanEdit { get; set; } +} diff --git a/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs b/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs new file mode 100644 index 00000000..1ba23339 --- /dev/null +++ b/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs @@ -0,0 +1,27 @@ +namespace Jellyfin.Api.Models.StartupDtos; + +/// <summary> +/// The startup configuration DTO. +/// </summary> +public class StartupConfigurationDto +{ + /// <summary> + /// Gets or sets the server name. + /// </summary> + public string? ServerName { get; set; } + + /// <summary> + /// Gets or sets UI language culture. + /// </summary> + public string? UICulture { get; set; } + + /// <summary> + /// Gets or sets the metadata country code. + /// </summary> + public string? MetadataCountryCode { get; set; } + + /// <summary> + /// Gets or sets the preferred language for the metadata. + /// </summary> + public string? PreferredMetadataLanguage { get; set; } +} diff --git a/Jellyfin.Api/Models/StartupDtos/StartupRemoteAccessDto.cs b/Jellyfin.Api/Models/StartupDtos/StartupRemoteAccessDto.cs new file mode 100644 index 00000000..2a1a312d --- /dev/null +++ b/Jellyfin.Api/Models/StartupDtos/StartupRemoteAccessDto.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace Jellyfin.Api.Models.StartupDtos; + +/// <summary> +/// Startup remote access dto. +/// </summary> +public class StartupRemoteAccessDto +{ + /// <summary> + /// Gets or sets a value indicating whether enable remote access. + /// </summary> + [Required] + public bool EnableRemoteAccess { get; set; } +} diff --git a/Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs b/Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs new file mode 100644 index 00000000..f473bbce --- /dev/null +++ b/Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs @@ -0,0 +1,17 @@ +namespace Jellyfin.Api.Models.StartupDtos; + +/// <summary> +/// The startup user DTO. +/// </summary> +public class StartupUserDto +{ + /// <summary> + /// Gets or sets the username. + /// </summary> + public string? Name { get; set; } + + /// <summary> + /// Gets or sets the user's password. + /// </summary> + public string? Password { get; set; } +} diff --git a/Jellyfin.Api/Models/StreamingDtos/HlsAudioRequestDto.cs b/Jellyfin.Api/Models/StreamingDtos/HlsAudioRequestDto.cs new file mode 100644 index 00000000..e1a51eec --- /dev/null +++ b/Jellyfin.Api/Models/StreamingDtos/HlsAudioRequestDto.cs @@ -0,0 +1,14 @@ +using MediaBrowser.Controller.Streaming; + +namespace Jellyfin.Api.Models.StreamingDtos; + +/// <summary> +/// The hls video request dto. +/// </summary> +public class HlsAudioRequestDto : StreamingRequestDto +{ + /// <summary> + /// Gets or sets a value indicating whether enable adaptive bitrate streaming. + /// </summary> + public bool EnableAdaptiveBitrateStreaming { get; set; } +} diff --git a/Jellyfin.Api/Models/StreamingDtos/HlsVideoRequestDto.cs b/Jellyfin.Api/Models/StreamingDtos/HlsVideoRequestDto.cs new file mode 100644 index 00000000..22da44bc --- /dev/null +++ b/Jellyfin.Api/Models/StreamingDtos/HlsVideoRequestDto.cs @@ -0,0 +1,14 @@ +using MediaBrowser.Controller.Streaming; + +namespace Jellyfin.Api.Models.StreamingDtos; + +/// <summary> +/// The hls video request dto. +/// </summary> +public class HlsVideoRequestDto : VideoRequestDto +{ + /// <summary> + /// Gets or sets a value indicating whether enable adaptive bitrate streaming. + /// </summary> + public bool EnableAdaptiveBitrateStreaming { get; set; } +} diff --git a/Jellyfin.Api/Models/SubtitleDtos/UploadSubtitleDto.cs b/Jellyfin.Api/Models/SubtitleDtos/UploadSubtitleDto.cs new file mode 100644 index 00000000..9fac1625 --- /dev/null +++ b/Jellyfin.Api/Models/SubtitleDtos/UploadSubtitleDto.cs @@ -0,0 +1,39 @@ +using System.ComponentModel.DataAnnotations; + +namespace Jellyfin.Api.Models.SubtitleDtos; + +/// <summary> +/// Upload subtitles dto. +/// </summary> +public class UploadSubtitleDto +{ + /// <summary> + /// Gets or sets the subtitle language. + /// </summary> + [Required] + public string Language { get; set; } = string.Empty; + + /// <summary> + /// Gets or sets the subtitle format. + /// </summary> + [Required] + public string Format { get; set; } = string.Empty; + + /// <summary> + /// Gets or sets a value indicating whether the subtitle is forced. + /// </summary> + [Required] + public bool IsForced { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the subtitle is for hearing impaired. + /// </summary> + [Required] + public bool IsHearingImpaired { get; set; } + + /// <summary> + /// Gets or sets the subtitle data. + /// </summary> + [Required] + public string Data { get; set; } = string.Empty; +} diff --git a/Jellyfin.Api/Models/SyncPlayDtos/BufferRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/BufferRequestDto.cs new file mode 100644 index 00000000..e7613911 --- /dev/null +++ b/Jellyfin.Api/Models/SyncPlayDtos/BufferRequestDto.cs @@ -0,0 +1,41 @@ +using System; + +namespace Jellyfin.Api.Models.SyncPlayDtos; + +/// <summary> +/// Class BufferRequestDto. +/// </summary> +public class BufferRequestDto +{ + /// <summary> + /// Initializes a new instance of the <see cref="BufferRequestDto"/> class. + /// </summary> + public BufferRequestDto() + { + PlaylistItemId = Guid.Empty; + } + + /// <summary> + /// Gets or sets when the request has been made by the client. + /// </summary> + /// <value>The date of the request.</value> + public DateTime When { get; set; } + + /// <summary> + /// Gets or sets the position ticks. + /// </summary> + /// <value>The position ticks.</value> + public long PositionTicks { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the client playback is unpaused. + /// </summary> + /// <value>The client playback status.</value> + public bool IsPlaying { get; set; } + + /// <summary> + /// Gets or sets the playlist item identifier of the playing item. + /// </summary> + /// <value>The playlist item identifier.</value> + public Guid PlaylistItemId { get; set; } +} diff --git a/Jellyfin.Api/Models/SyncPlayDtos/IgnoreWaitRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/IgnoreWaitRequestDto.cs new file mode 100644 index 00000000..8ccd831b --- /dev/null +++ b/Jellyfin.Api/Models/SyncPlayDtos/IgnoreWaitRequestDto.cs @@ -0,0 +1,13 @@ +namespace Jellyfin.Api.Models.SyncPlayDtos; + +/// <summary> +/// Class IgnoreWaitRequestDto. +/// </summary> +public class IgnoreWaitRequestDto +{ + /// <summary> + /// Gets or sets a value indicating whether the client should be ignored. + /// </summary> + /// <value>The client group-wait status.</value> + public bool IgnoreWait { get; set; } +} diff --git a/Jellyfin.Api/Models/SyncPlayDtos/JoinGroupRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/JoinGroupRequestDto.cs new file mode 100644 index 00000000..89ba511a --- /dev/null +++ b/Jellyfin.Api/Models/SyncPlayDtos/JoinGroupRequestDto.cs @@ -0,0 +1,15 @@ +using System; + +namespace Jellyfin.Api.Models.SyncPlayDtos; + +/// <summary> +/// Class JoinGroupRequestDto. +/// </summary> +public class JoinGroupRequestDto +{ + /// <summary> + /// Gets or sets the group identifier. + /// </summary> + /// <value>The identifier of the group to join.</value> + public Guid GroupId { get; set; } +} diff --git a/Jellyfin.Api/Models/SyncPlayDtos/MovePlaylistItemRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/MovePlaylistItemRequestDto.cs new file mode 100644 index 00000000..220d147f --- /dev/null +++ b/Jellyfin.Api/Models/SyncPlayDtos/MovePlaylistItemRequestDto.cs @@ -0,0 +1,29 @@ +using System; + +namespace Jellyfin.Api.Models.SyncPlayDtos; + +/// <summary> +/// Class MovePlaylistItemRequestDto. +/// </summary> +public class MovePlaylistItemRequestDto +{ + /// <summary> + /// Initializes a new instance of the <see cref="MovePlaylistItemRequestDto"/> class. + /// </summary> + public MovePlaylistItemRequestDto() + { + PlaylistItemId = Guid.Empty; + } + + /// <summary> + /// Gets or sets the playlist identifier of the item. + /// </summary> + /// <value>The playlist identifier of the item.</value> + public Guid PlaylistItemId { get; set; } + + /// <summary> + /// Gets or sets the new position. + /// </summary> + /// <value>The new position.</value> + public int NewIndex { get; set; } +} diff --git a/Jellyfin.Api/Models/SyncPlayDtos/NewGroupRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/NewGroupRequestDto.cs new file mode 100644 index 00000000..32a3bb44 --- /dev/null +++ b/Jellyfin.Api/Models/SyncPlayDtos/NewGroupRequestDto.cs @@ -0,0 +1,21 @@ +namespace Jellyfin.Api.Models.SyncPlayDtos; + +/// <summary> +/// Class NewGroupRequestDto. +/// </summary> +public class NewGroupRequestDto +{ + /// <summary> + /// Initializes a new instance of the <see cref="NewGroupRequestDto"/> class. + /// </summary> + public NewGroupRequestDto() + { + GroupName = string.Empty; + } + + /// <summary> + /// Gets or sets the group name. + /// </summary> + /// <value>The name of the new group.</value> + public string GroupName { get; set; } +} diff --git a/Jellyfin.Api/Models/SyncPlayDtos/NextItemRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/NextItemRequestDto.cs new file mode 100644 index 00000000..b5223af5 --- /dev/null +++ b/Jellyfin.Api/Models/SyncPlayDtos/NextItemRequestDto.cs @@ -0,0 +1,23 @@ +using System; + +namespace Jellyfin.Api.Models.SyncPlayDtos; + +/// <summary> +/// Class NextItemRequestDto. +/// </summary> +public class NextItemRequestDto +{ + /// <summary> + /// Initializes a new instance of the <see cref="NextItemRequestDto"/> class. + /// </summary> + public NextItemRequestDto() + { + PlaylistItemId = Guid.Empty; + } + + /// <summary> + /// Gets or sets the playing item identifier. + /// </summary> + /// <value>The playing item identifier.</value> + public Guid PlaylistItemId { get; set; } +} diff --git a/Jellyfin.Api/Models/SyncPlayDtos/PingRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/PingRequestDto.cs new file mode 100644 index 00000000..f1339505 --- /dev/null +++ b/Jellyfin.Api/Models/SyncPlayDtos/PingRequestDto.cs @@ -0,0 +1,13 @@ +namespace Jellyfin.Api.Models.SyncPlayDtos; + +/// <summary> +/// Class PingRequestDto. +/// </summary> +public class PingRequestDto +{ + /// <summary> + /// Gets or sets the ping time. + /// </summary> + /// <value>The ping time.</value> + public long Ping { get; set; } +} diff --git a/Jellyfin.Api/Models/SyncPlayDtos/PlayRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/PlayRequestDto.cs new file mode 100644 index 00000000..e0edaf5e --- /dev/null +++ b/Jellyfin.Api/Models/SyncPlayDtos/PlayRequestDto.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace Jellyfin.Api.Models.SyncPlayDtos; + +/// <summary> +/// Class PlayRequestDto. +/// </summary> +public class PlayRequestDto +{ + /// <summary> + /// Initializes a new instance of the <see cref="PlayRequestDto"/> class. + /// </summary> + public PlayRequestDto() + { + PlayingQueue = Array.Empty<Guid>(); + } + + /// <summary> + /// Gets or sets the playing queue. + /// </summary> + /// <value>The playing queue.</value> + public IReadOnlyList<Guid> PlayingQueue { get; set; } + + /// <summary> + /// Gets or sets the position of the playing item in the queue. + /// </summary> + /// <value>The playing item position.</value> + public int PlayingItemPosition { get; set; } + + /// <summary> + /// Gets or sets the start position ticks. + /// </summary> + /// <value>The start position ticks.</value> + public long StartPositionTicks { get; set; } +} diff --git a/Jellyfin.Api/Models/SyncPlayDtos/PreviousItemRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/PreviousItemRequestDto.cs new file mode 100644 index 00000000..f52bd7f4 --- /dev/null +++ b/Jellyfin.Api/Models/SyncPlayDtos/PreviousItemRequestDto.cs @@ -0,0 +1,23 @@ +using System; + +namespace Jellyfin.Api.Models.SyncPlayDtos; + +/// <summary> +/// Class PreviousItemRequestDto. +/// </summary> +public class PreviousItemRequestDto +{ + /// <summary> + /// Initializes a new instance of the <see cref="PreviousItemRequestDto"/> class. + /// </summary> + public PreviousItemRequestDto() + { + PlaylistItemId = Guid.Empty; + } + + /// <summary> + /// Gets or sets the playing item identifier. + /// </summary> + /// <value>The playing item identifier.</value> + public Guid PlaylistItemId { get; set; } +} diff --git a/Jellyfin.Api/Models/SyncPlayDtos/QueueRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/QueueRequestDto.cs new file mode 100644 index 00000000..c2c2fba0 --- /dev/null +++ b/Jellyfin.Api/Models/SyncPlayDtos/QueueRequestDto.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Model.SyncPlay; + +namespace Jellyfin.Api.Models.SyncPlayDtos; + +/// <summary> +/// Class QueueRequestDto. +/// </summary> +public class QueueRequestDto +{ + /// <summary> + /// Initializes a new instance of the <see cref="QueueRequestDto"/> class. + /// </summary> + public QueueRequestDto() + { + ItemIds = Array.Empty<Guid>(); + } + + /// <summary> + /// Gets or sets the items to enqueue. + /// </summary> + /// <value>The items to enqueue.</value> + public IReadOnlyList<Guid> ItemIds { get; set; } + + /// <summary> + /// Gets or sets the mode in which to add the new items. + /// </summary> + /// <value>The enqueue mode.</value> + public GroupQueueMode Mode { get; set; } +} diff --git a/Jellyfin.Api/Models/SyncPlayDtos/ReadyRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/ReadyRequestDto.cs new file mode 100644 index 00000000..d8be75ef --- /dev/null +++ b/Jellyfin.Api/Models/SyncPlayDtos/ReadyRequestDto.cs @@ -0,0 +1,41 @@ +using System; + +namespace Jellyfin.Api.Models.SyncPlayDtos; + +/// <summary> +/// Class ReadyRequest. +/// </summary> +public class ReadyRequestDto +{ + /// <summary> + /// Initializes a new instance of the <see cref="ReadyRequestDto"/> class. + /// </summary> + public ReadyRequestDto() + { + PlaylistItemId = Guid.Empty; + } + + /// <summary> + /// Gets or sets when the request has been made by the client. + /// </summary> + /// <value>The date of the request.</value> + public DateTime When { get; set; } + + /// <summary> + /// Gets or sets the position ticks. + /// </summary> + /// <value>The position ticks.</value> + public long PositionTicks { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the client playback is unpaused. + /// </summary> + /// <value>The client playback status.</value> + public bool IsPlaying { get; set; } + + /// <summary> + /// Gets or sets the playlist item identifier of the playing item. + /// </summary> + /// <value>The playlist item identifier.</value> + public Guid PlaylistItemId { get; set; } +} diff --git a/Jellyfin.Api/Models/SyncPlayDtos/RemoveFromPlaylistRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/RemoveFromPlaylistRequestDto.cs new file mode 100644 index 00000000..2c723427 --- /dev/null +++ b/Jellyfin.Api/Models/SyncPlayDtos/RemoveFromPlaylistRequestDto.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace Jellyfin.Api.Models.SyncPlayDtos; + +/// <summary> +/// Class RemoveFromPlaylistRequestDto. +/// </summary> +public class RemoveFromPlaylistRequestDto +{ + /// <summary> + /// Initializes a new instance of the <see cref="RemoveFromPlaylistRequestDto"/> class. + /// </summary> + public RemoveFromPlaylistRequestDto() + { + PlaylistItemIds = Array.Empty<Guid>(); + } + + /// <summary> + /// Gets or sets the playlist identifiers of the items. Ignored when clearing the playlist. + /// </summary> + /// <value>The playlist identifiers of the items.</value> + public IReadOnlyList<Guid> PlaylistItemIds { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the entire playlist should be cleared. + /// </summary> + /// <value>Whether the entire playlist should be cleared.</value> + public bool ClearPlaylist { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the playing item should be removed as well. Used only when clearing the playlist. + /// </summary> + /// <value>Whether the playing item should be removed as well.</value> + public bool ClearPlayingItem { get; set; } +} diff --git a/Jellyfin.Api/Models/SyncPlayDtos/SeekRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/SeekRequestDto.cs new file mode 100644 index 00000000..f461417e --- /dev/null +++ b/Jellyfin.Api/Models/SyncPlayDtos/SeekRequestDto.cs @@ -0,0 +1,13 @@ +namespace Jellyfin.Api.Models.SyncPlayDtos; + +/// <summary> +/// Class SeekRequestDto. +/// </summary> +public class SeekRequestDto +{ + /// <summary> + /// Gets or sets the position ticks. + /// </summary> + /// <value>The position ticks.</value> + public long PositionTicks { get; set; } +} diff --git a/Jellyfin.Api/Models/SyncPlayDtos/SetPlaylistItemRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/SetPlaylistItemRequestDto.cs new file mode 100644 index 00000000..40e66503 --- /dev/null +++ b/Jellyfin.Api/Models/SyncPlayDtos/SetPlaylistItemRequestDto.cs @@ -0,0 +1,23 @@ +using System; + +namespace Jellyfin.Api.Models.SyncPlayDtos; + +/// <summary> +/// Class SetPlaylistItemRequestDto. +/// </summary> +public class SetPlaylistItemRequestDto +{ + /// <summary> + /// Initializes a new instance of the <see cref="SetPlaylistItemRequestDto"/> class. + /// </summary> + public SetPlaylistItemRequestDto() + { + PlaylistItemId = Guid.Empty; + } + + /// <summary> + /// Gets or sets the playlist identifier of the playing item. + /// </summary> + /// <value>The playlist identifier of the playing item.</value> + public Guid PlaylistItemId { get; set; } +} diff --git a/Jellyfin.Api/Models/SyncPlayDtos/SetRepeatModeRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/SetRepeatModeRequestDto.cs new file mode 100644 index 00000000..387d1ea7 --- /dev/null +++ b/Jellyfin.Api/Models/SyncPlayDtos/SetRepeatModeRequestDto.cs @@ -0,0 +1,15 @@ +using MediaBrowser.Model.SyncPlay; + +namespace Jellyfin.Api.Models.SyncPlayDtos; + +/// <summary> +/// Class SetRepeatModeRequestDto. +/// </summary> +public class SetRepeatModeRequestDto +{ + /// <summary> + /// Gets or sets the repeat mode. + /// </summary> + /// <value>The repeat mode.</value> + public GroupRepeatMode Mode { get; set; } +} diff --git a/Jellyfin.Api/Models/SyncPlayDtos/SetShuffleModeRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/SetShuffleModeRequestDto.cs new file mode 100644 index 00000000..a67e3958 --- /dev/null +++ b/Jellyfin.Api/Models/SyncPlayDtos/SetShuffleModeRequestDto.cs @@ -0,0 +1,15 @@ +using MediaBrowser.Model.SyncPlay; + +namespace Jellyfin.Api.Models.SyncPlayDtos; + +/// <summary> +/// Class SetShuffleModeRequestDto. +/// </summary> +public class SetShuffleModeRequestDto +{ + /// <summary> + /// Gets or sets the shuffle mode. + /// </summary> + /// <value>The shuffle mode.</value> + public GroupShuffleMode Mode { get; set; } +} diff --git a/Jellyfin.Api/Models/SystemInfoDtos/FolderStorageDto.cs b/Jellyfin.Api/Models/SystemInfoDtos/FolderStorageDto.cs new file mode 100644 index 00000000..00a96589 --- /dev/null +++ b/Jellyfin.Api/Models/SystemInfoDtos/FolderStorageDto.cs @@ -0,0 +1,46 @@ +using MediaBrowser.Model.System; + +namespace Jellyfin.Api.Models.SystemInfoDtos; + +/// <summary> +/// Contains information about a specific folder. +/// </summary> +public record FolderStorageDto +{ + /// <summary> + /// Gets the path of the folder in question. + /// </summary> + public required string Path { get; init; } + + /// <summary> + /// Gets the free space of the underlying storage device of the <see cref="Path"/>. + /// </summary> + public long FreeSpace { get; init; } + + /// <summary> + /// Gets the used space of the underlying storage device of the <see cref="Path"/>. + /// </summary> + public long UsedSpace { get; init; } + + /// <summary> + /// Gets the kind of storage device of the <see cref="Path"/>. + /// </summary> + public string? StorageType { get; init; } + + /// <summary> + /// Gets the Device Identifier. + /// </summary> + public string? DeviceId { get; init; } + + internal static FolderStorageDto FromFolderStorageInfo(FolderStorageInfo model) + { + return new() + { + Path = model.Path, + FreeSpace = model.FreeSpace, + UsedSpace = model.UsedSpace, + StorageType = model.StorageType, + DeviceId = model.DeviceId + }; + } +} diff --git a/Jellyfin.Api/Models/SystemInfoDtos/LibraryStorageDto.cs b/Jellyfin.Api/Models/SystemInfoDtos/LibraryStorageDto.cs new file mode 100644 index 00000000..c138324d --- /dev/null +++ b/Jellyfin.Api/Models/SystemInfoDtos/LibraryStorageDto.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Model.System; + +namespace Jellyfin.Api.Models.SystemInfoDtos; + +/// <summary> +/// Contains informations about a libraries storage informations. +/// </summary> +public record LibraryStorageDto +{ + /// <summary> + /// Gets or sets the Library Id. + /// </summary> + public required Guid Id { get; set; } + + /// <summary> + /// Gets or sets the name of the library. + /// </summary> + public required string Name { get; set; } + + /// <summary> + /// Gets or sets the storage informations about the folders used in a library. + /// </summary> + public required IReadOnlyCollection<FolderStorageDto> Folders { get; set; } + + internal static LibraryStorageDto FromLibraryStorageModel(LibraryStorageInfo model) + { + return new() + { + Id = model.Id, + Name = model.Name, + Folders = model.Folders.Select(FolderStorageDto.FromFolderStorageInfo).ToArray() + }; + } +} diff --git a/Jellyfin.Api/Models/SystemInfoDtos/SystemStorageDto.cs b/Jellyfin.Api/Models/SystemInfoDtos/SystemStorageDto.cs new file mode 100644 index 00000000..a0904243 --- /dev/null +++ b/Jellyfin.Api/Models/SystemInfoDtos/SystemStorageDto.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Model.System; + +namespace Jellyfin.Api.Models.SystemInfoDtos; + +/// <summary> +/// Contains informations about the systems storage. +/// </summary> +public record SystemStorageDto +{ + /// <summary> + /// Gets or sets the Storage information of the program data folder. + /// </summary> + public required FolderStorageDto ProgramDataFolder { get; set; } + + /// <summary> + /// Gets or sets the Storage information of the web UI resources folder. + /// </summary> + public required FolderStorageDto WebFolder { get; set; } + + /// <summary> + /// Gets or sets the Storage information of the folder where images are cached. + /// </summary> + public required FolderStorageDto ImageCacheFolder { get; set; } + + /// <summary> + /// Gets or sets the Storage information of the cache folder. + /// </summary> + public required FolderStorageDto CacheFolder { get; set; } + + /// <summary> + /// Gets or sets the Storage information of the folder where logfiles are saved to. + /// </summary> + public required FolderStorageDto LogFolder { get; set; } + + /// <summary> + /// Gets or sets the Storage information of the folder where metadata is stored. + /// </summary> + public required FolderStorageDto InternalMetadataFolder { get; set; } + + /// <summary> + /// Gets or sets the Storage information of the transcoding cache. + /// </summary> + public required FolderStorageDto TranscodingTempFolder { get; set; } + + /// <summary> + /// Gets or sets the storage informations of all libraries. + /// </summary> + public required IReadOnlyCollection<LibraryStorageDto> Libraries { get; set; } + + internal static SystemStorageDto FromSystemStorageInfo(SystemStorageInfo model) + { + return new SystemStorageDto() + { + ProgramDataFolder = FolderStorageDto.FromFolderStorageInfo(model.ProgramDataFolder), + WebFolder = FolderStorageDto.FromFolderStorageInfo(model.WebFolder), + ImageCacheFolder = FolderStorageDto.FromFolderStorageInfo(model.ImageCacheFolder), + CacheFolder = FolderStorageDto.FromFolderStorageInfo(model.CacheFolder), + LogFolder = FolderStorageDto.FromFolderStorageInfo(model.LogFolder), + InternalMetadataFolder = FolderStorageDto.FromFolderStorageInfo(model.InternalMetadataFolder), + TranscodingTempFolder = FolderStorageDto.FromFolderStorageInfo(model.TranscodingTempFolder), + Libraries = model.Libraries.Select(LibraryStorageDto.FromLibraryStorageModel).ToArray() + }; + } +} diff --git a/Jellyfin.Api/Models/UserDtos/AuthenticateUserByName.cs b/Jellyfin.Api/Models/UserDtos/AuthenticateUserByName.cs new file mode 100644 index 00000000..6ab99726 --- /dev/null +++ b/Jellyfin.Api/Models/UserDtos/AuthenticateUserByName.cs @@ -0,0 +1,17 @@ +namespace Jellyfin.Api.Models.UserDtos; + +/// <summary> +/// The authenticate user by name request body. +/// </summary> +public class AuthenticateUserByName +{ + /// <summary> + /// Gets or sets the username. + /// </summary> + public string? Username { get; set; } + + /// <summary> + /// Gets or sets the plain text password. + /// </summary> + public string? Pw { get; set; } +} diff --git a/Jellyfin.Api/Models/UserDtos/CreateUserByName.cs b/Jellyfin.Api/Models/UserDtos/CreateUserByName.cs new file mode 100644 index 00000000..484a0f12 --- /dev/null +++ b/Jellyfin.Api/Models/UserDtos/CreateUserByName.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations; + +namespace Jellyfin.Api.Models.UserDtos; + +/// <summary> +/// The create user by name request body. +/// </summary> +public class CreateUserByName +{ + /// <summary> + /// Gets or sets the username. + /// </summary> + [Required] + public required string Name { get; set; } + + /// <summary> + /// Gets or sets the password. + /// </summary> + public string? Password { get; set; } +} diff --git a/Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs b/Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs new file mode 100644 index 00000000..e91894db --- /dev/null +++ b/Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace Jellyfin.Api.Models.UserDtos; + +/// <summary> +/// Forgot Password request body DTO. +/// </summary> +public class ForgotPasswordDto +{ + /// <summary> + /// Gets or sets the entered username to have its password reset. + /// </summary> + [Required] + public required string EnteredUsername { get; set; } +} diff --git a/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs b/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs new file mode 100644 index 00000000..701b8508 --- /dev/null +++ b/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace Jellyfin.Api.Models.UserDtos; + +/// <summary> +/// Forgot Password Pin enter request body DTO. +/// </summary> +public class ForgotPasswordPinDto +{ + /// <summary> + /// Gets or sets the entered pin to have the password reset. + /// </summary> + [Required] + public required string Pin { get; set; } +} diff --git a/Jellyfin.Api/Models/UserDtos/QuickConnectDto.cs b/Jellyfin.Api/Models/UserDtos/QuickConnectDto.cs new file mode 100644 index 00000000..1b14351a --- /dev/null +++ b/Jellyfin.Api/Models/UserDtos/QuickConnectDto.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace Jellyfin.Api.Models.UserDtos; + +/// <summary> +/// The quick connect request body. +/// </summary> +public class QuickConnectDto +{ + /// <summary> + /// Gets or sets the quick connect secret. + /// </summary> + [Required] + public string Secret { get; set; } = null!; +} diff --git a/Jellyfin.Api/Models/UserDtos/UpdateUserPassword.cs b/Jellyfin.Api/Models/UserDtos/UpdateUserPassword.cs new file mode 100644 index 00000000..0576a8aa --- /dev/null +++ b/Jellyfin.Api/Models/UserDtos/UpdateUserPassword.cs @@ -0,0 +1,27 @@ +namespace Jellyfin.Api.Models.UserDtos; + +/// <summary> +/// The update user password request body. +/// </summary> +public class UpdateUserPassword +{ + /// <summary> + /// Gets or sets the current sha1-hashed password. + /// </summary> + public string? CurrentPassword { get; set; } + + /// <summary> + /// Gets or sets the current plain text password. + /// </summary> + public string? CurrentPw { get; set; } + + /// <summary> + /// Gets or sets the new plain text password. + /// </summary> + public string? NewPw { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to reset the password. + /// </summary> + public bool ResetPassword { get; set; } +} diff --git a/Jellyfin.Api/Models/UserViewDtos/SpecialViewOptionDto.cs b/Jellyfin.Api/Models/UserViewDtos/SpecialViewOptionDto.cs new file mode 100644 index 00000000..3d368879 --- /dev/null +++ b/Jellyfin.Api/Models/UserViewDtos/SpecialViewOptionDto.cs @@ -0,0 +1,17 @@ +namespace Jellyfin.Api.Models.UserViewDtos; + +/// <summary> +/// Special view option dto. +/// </summary> +public class SpecialViewOptionDto +{ + /// <summary> + /// Gets or sets view option name. + /// </summary> + public string? Name { get; set; } + + /// <summary> + /// Gets or sets view option id. + /// </summary> + public string? Id { get; set; } +} diff --git a/Jellyfin.Api/Results/OkResultOfT.cs b/Jellyfin.Api/Results/OkResultOfT.cs new file mode 100644 index 00000000..f60cbbce --- /dev/null +++ b/Jellyfin.Api/Results/OkResultOfT.cs @@ -0,0 +1,21 @@ +#pragma warning disable SA1649 // File name should match type name. + +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Results; + +/// <summary> +/// Ok result with type specified. +/// </summary> +/// <typeparam name="T">The type to return.</typeparam> +public class OkResult<T> : OkObjectResult +{ + /// <summary> + /// Initializes a new instance of the <see cref="OkResult{T}"/> class. + /// </summary> + /// <param name="value">The value to return.</param> + public OkResult(T value) + : base(value) + { + } +} diff --git a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs new file mode 100644 index 00000000..60379f41 --- /dev/null +++ b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs @@ -0,0 +1,88 @@ +using System; +using System.Threading.Tasks; +using Jellyfin.Data; +using Jellyfin.Data.Events; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Session; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.WebSocketListeners; + +/// <summary> +/// Class ActivityLogWebSocketListener. +/// </summary> +public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<ActivityLogEntry[], WebSocketListenerState> +{ + /// <summary> + /// The _kernel. + /// </summary> + private readonly IActivityManager _activityManager; + + private bool _disposed; + + /// <summary> + /// Initializes a new instance of the <see cref="ActivityLogWebSocketListener"/> class. + /// </summary> + /// <param name="logger">Instance of the <see cref="ILogger{ActivityLogWebSocketListener}"/> interface.</param> + /// <param name="activityManager">Instance of the <see cref="IActivityManager"/> interface.</param> + public ActivityLogWebSocketListener(ILogger<ActivityLogWebSocketListener> logger, IActivityManager activityManager) + : base(logger) + { + _activityManager = activityManager; + _activityManager.EntryCreated += OnEntryCreated; + } + + /// <inheritdoc /> + protected override SessionMessageType Type => SessionMessageType.ActivityLogEntry; + + /// <inheritdoc /> + protected override SessionMessageType StartType => SessionMessageType.ActivityLogEntryStart; + + /// <inheritdoc /> + protected override SessionMessageType StopType => SessionMessageType.ActivityLogEntryStop; + + /// <summary> + /// Gets the data to send. + /// </summary> + /// <returns>Task{SystemInfo}.</returns> + protected override Task<ActivityLogEntry[]> GetDataToSend() + { + return Task.FromResult(Array.Empty<ActivityLogEntry>()); + } + + /// <inheritdoc /> + protected override async ValueTask DisposeAsyncCore() + { + if (!_disposed) + { + _activityManager.EntryCreated -= OnEntryCreated; + _disposed = true; + } + + await base.DisposeAsyncCore().ConfigureAwait(false); + } + + /// <summary> + /// Starts sending messages over an activity log web socket. + /// </summary> + /// <param name="message">The message.</param> + protected override void Start(WebSocketMessageInfo message) + { + if (!message.Connection.AuthorizationInfo.IsApiKey + && (message.Connection.AuthorizationInfo.User is null + || !message.Connection.AuthorizationInfo.User.HasPermission(PermissionKind.IsAdministrator))) + { + throw new AuthenticationException("Only admin users can retrieve the activity log."); + } + + base.Start(message); + } + + private void OnEntryCreated(object? sender, GenericEventArgs<ActivityLogEntry> e) + { + SendData(true); + } +} diff --git a/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs new file mode 100644 index 00000000..dd928621 --- /dev/null +++ b/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs @@ -0,0 +1,89 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Data.Events; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Session; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.WebSocketListeners; + +/// <summary> +/// Class ScheduledTasksWebSocketListener. +/// </summary> +public class ScheduledTasksWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<TaskInfo>, WebSocketListenerState> +{ + /// <summary> + /// Gets or sets the task manager. + /// </summary> + /// <value>The task manager.</value> + private readonly ITaskManager _taskManager; + + private bool _disposed; + + /// <summary> + /// Initializes a new instance of the <see cref="ScheduledTasksWebSocketListener"/> class. + /// </summary> + /// <param name="logger">Instance of the <see cref="ILogger{ScheduledTasksWebSocketListener}"/> interface.</param> + /// <param name="taskManager">Instance of the <see cref="ITaskManager"/> interface.</param> + public ScheduledTasksWebSocketListener(ILogger<ScheduledTasksWebSocketListener> logger, ITaskManager taskManager) + : base(logger) + { + _taskManager = taskManager; + + _taskManager.TaskExecuting += OnTaskExecuting; + _taskManager.TaskCompleted += OnTaskCompleted; + } + + /// <inheritdoc /> + protected override SessionMessageType Type => SessionMessageType.ScheduledTasksInfo; + + /// <inheritdoc /> + protected override SessionMessageType StartType => SessionMessageType.ScheduledTasksInfoStart; + + /// <inheritdoc /> + protected override SessionMessageType StopType => SessionMessageType.ScheduledTasksInfoStop; + + /// <summary> + /// Gets the data to send. + /// </summary> + /// <returns>Task{IEnumerable{TaskInfo}}.</returns> + protected override Task<IEnumerable<TaskInfo>> GetDataToSend() + { + return Task.FromResult(_taskManager.ScheduledTasks + .OrderBy(i => i.Name) + .Select(ScheduledTaskHelpers.GetTaskInfo) + .Where(i => !i.IsHidden)); + } + + /// <inheritdoc /> + protected override async ValueTask DisposeAsyncCore() + { + if (!_disposed) + { + _taskManager.TaskExecuting -= OnTaskExecuting; + _taskManager.TaskCompleted -= OnTaskCompleted; + _disposed = true; + } + + await base.DisposeAsyncCore().ConfigureAwait(false); + } + + private void OnTaskCompleted(object? sender, TaskCompletionEventArgs e) + { + e.Task.TaskProgress -= OnTaskProgress; + SendData(true); + } + + private void OnTaskExecuting(object? sender, GenericEventArgs<IScheduledTaskWorker> e) + { + SendData(true); + e.Argument.TaskProgress += OnTaskProgress; + } + + private void OnTaskProgress(object? sender, GenericEventArgs<double> e) + { + SendData(false); + } +} diff --git a/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs new file mode 100644 index 00000000..db24c974 --- /dev/null +++ b/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs @@ -0,0 +1,145 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Data; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Session; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.WebSocketListeners; + +/// <summary> +/// Class SessionInfoWebSocketListener. +/// </summary> +public class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfoDto>, WebSocketListenerState> +{ + private readonly ISessionManager _sessionManager; + private bool _disposed; + + /// <summary> + /// Initializes a new instance of the <see cref="SessionInfoWebSocketListener"/> class. + /// </summary> + /// <param name="logger">Instance of the <see cref="ILogger{SessionInfoWebSocketListener}"/> interface.</param> + /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param> + public SessionInfoWebSocketListener(ILogger<SessionInfoWebSocketListener> logger, ISessionManager sessionManager) + : base(logger) + { + _sessionManager = sessionManager; + + _sessionManager.SessionStarted += OnSessionManagerSessionStarted; + _sessionManager.SessionEnded += OnSessionManagerSessionEnded; + _sessionManager.PlaybackStart += OnSessionManagerPlaybackStart; + _sessionManager.PlaybackStopped += OnSessionManagerPlaybackStopped; + _sessionManager.PlaybackProgress += OnSessionManagerPlaybackProgress; + _sessionManager.CapabilitiesChanged += OnSessionManagerCapabilitiesChanged; + _sessionManager.SessionActivity += OnSessionManagerSessionActivity; + } + + /// <inheritdoc /> + protected override SessionMessageType Type => SessionMessageType.Sessions; + + /// <inheritdoc /> + protected override SessionMessageType StartType => SessionMessageType.SessionsStart; + + /// <inheritdoc /> + protected override SessionMessageType StopType => SessionMessageType.SessionsStop; + + /// <summary> + /// Gets the data to send. + /// </summary> + /// <returns>Task{SystemInfo}.</returns> + protected override Task<IEnumerable<SessionInfoDto>> GetDataToSend() + { + return Task.FromResult(_sessionManager.Sessions.Select(_sessionManager.ToSessionInfoDto)); + } + + /// <inheritdoc /> + protected override Task<IEnumerable<SessionInfoDto>> GetDataToSendForConnection(IWebSocketConnection connection) + { + var sessions = _sessionManager.Sessions; + + // For non-admin users, filter the sessions to only include their own sessions + if (connection.AuthorizationInfo?.User is not null && + !connection.AuthorizationInfo.IsApiKey && + !connection.AuthorizationInfo.User.HasPermission(PermissionKind.IsAdministrator)) + { + var userId = connection.AuthorizationInfo.User.Id; + sessions = sessions.Where(s => s.UserId.Equals(userId) || s.ContainsUser(userId)); + } + + return Task.FromResult(sessions.Select(_sessionManager.ToSessionInfoDto)); + } + + /// <inheritdoc /> + protected override async ValueTask DisposeAsyncCore() + { + if (!_disposed) + { + _sessionManager.SessionStarted -= OnSessionManagerSessionStarted; + _sessionManager.SessionEnded -= OnSessionManagerSessionEnded; + _sessionManager.PlaybackStart -= OnSessionManagerPlaybackStart; + _sessionManager.PlaybackStopped -= OnSessionManagerPlaybackStopped; + _sessionManager.PlaybackProgress -= OnSessionManagerPlaybackProgress; + _sessionManager.CapabilitiesChanged -= OnSessionManagerCapabilitiesChanged; + _sessionManager.SessionActivity -= OnSessionManagerSessionActivity; + _disposed = true; + } + + await base.DisposeAsyncCore().ConfigureAwait(false); + } + + /// <summary> + /// Starts sending messages over a session info web socket. + /// </summary> + /// <param name="message">The message.</param> + protected override void Start(WebSocketMessageInfo message) + { + // Allow all authenticated users to subscribe to session information + if (message.Connection.AuthorizationInfo.User is null && !message.Connection.AuthorizationInfo.IsApiKey) + { + throw new AuthenticationException("User must be authenticated to subscribe to session Information."); + } + + base.Start(message); + } + + private void OnSessionManagerSessionActivity(object? sender, SessionEventArgs e) + { + SendData(false); + } + + private void OnSessionManagerCapabilitiesChanged(object? sender, SessionEventArgs e) + { + SendData(true); + } + + private void OnSessionManagerPlaybackProgress(object? sender, PlaybackProgressEventArgs e) + { + SendData(!e.IsAutomated); + } + + private void OnSessionManagerPlaybackStopped(object? sender, PlaybackStopEventArgs e) + { + SendData(true); + } + + private void OnSessionManagerPlaybackStart(object? sender, PlaybackProgressEventArgs e) + { + SendData(true); + } + + private void OnSessionManagerSessionEnded(object? sender, SessionEventArgs e) + { + SendData(true); + } + + private void OnSessionManagerSessionStarted(object? sender, SessionEventArgs e) + { + SendData(true); + } +} diff --git a/Jellyfin.Api/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs b/Jellyfin.Api/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs new file mode 100644 index 00000000..925b1351 --- /dev/null +++ b/Jellyfin.Api/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// <autogenerated /> +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")] diff --git a/Jellyfin.Api/obj/Debug/net10.0/Jellyfin.Api.AssemblyInfo.cs b/Jellyfin.Api/obj/Debug/net10.0/Jellyfin.Api.AssemblyInfo.cs new file mode 100644 index 00000000..6d09d9e7 --- /dev/null +++ b/Jellyfin.Api/obj/Debug/net10.0/Jellyfin.Api.AssemblyInfo.cs @@ -0,0 +1,23 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Jellyfin.Api.Tests")] +[assembly: System.Reflection.AssemblyCompanyAttribute("Jellyfin.Api")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("Jellyfin.Api")] +[assembly: System.Reflection.AssemblyTitleAttribute("Jellyfin.Api")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Jellyfin.Api/obj/Debug/net10.0/Jellyfin.Api.AssemblyInfoInputs.cache b/Jellyfin.Api/obj/Debug/net10.0/Jellyfin.Api.AssemblyInfoInputs.cache new file mode 100644 index 00000000..d5bd44c3 --- /dev/null +++ b/Jellyfin.Api/obj/Debug/net10.0/Jellyfin.Api.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +52d1a04b328522d6069271abe93c5fca0460a15387cb3d4ab6802f32d6c26f26 diff --git a/Jellyfin.Api/obj/Debug/net10.0/Jellyfin.Api.GeneratedMSBuildEditorConfig.editorconfig b/Jellyfin.Api/obj/Debug/net10.0/Jellyfin.Api.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 00000000..525d22e2 --- /dev/null +++ b/Jellyfin.Api/obj/Debug/net10.0/Jellyfin.Api.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,27 @@ +is_global = true +build_property.TargetFramework = net10.0 +build_property.TargetFramework = net10.0 +build_property.TargetFrameworkIdentifier = .NETCoreApp +build_property.TargetFrameworkIdentifier = .NETCoreApp +build_property.TargetFrameworkVersion = v10.0 +build_property.TargetFrameworkVersion = v10.0 +build_property.TargetPlatformMinVersion = +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = Jellyfin.Api +build_property.ProjectDir = /srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 10.0 +build_property.EnableCodeStyleSeverity = diff --git a/Jellyfin.Api/obj/Debug/net10.0/Jellyfin.Api.assets.cache b/Jellyfin.Api/obj/Debug/net10.0/Jellyfin.Api.assets.cache new file mode 100644 index 0000000000000000000000000000000000000000..ac8686d75fa23692da50d36258c9717c94476e2a GIT binary patch literal 28557 zcmd5_S$7=Ab%sKMG-(SmDN)u&k(5YnCVQ}u6e(MlKrAFAfuspSjx1YY&rAboVy0&@ zJs=4>i(}bw94TI6$4lZh`@ZknOWu=n@|<6gzmS)Fx4U}k>z=Bf#$W&f=hT_$np^jN zwRYWG_ZF_cesS_cLqkLVbMiB%zWDgN|NHrw_x)hk)9;sm_~fVmd+yTRJOBA#|M#tH z|1tdDTSG%11YNbX(x`i-+cz757nYo0ZP{y=nr>y)UB<It^}}W(bjvl*4cyxLU9T0E zPCKK{xsraaHrfFSRl?HTbccL0A$O{^+D}1{{N0Bn-*+K>?Y>;eb-&VTgpH-PQ)yIV z;h`ToWj6@Cs@wFn_|wi95E=2zcbH6pNd!rlT!@%VHhm+=-3sI|KEHkMKu#^JxUJ>} zu)bFms~5EW_WDxGt$Vi{t<_4S<vF@J$41e;6Fr)9CSoz0u$U4S#>U*E8gnl`e_R;g zowgT*ej^Cc`N5LEyw-Btcy!8sQ1x1+Vh}ZxsT7k75ff*jvDT`17rnq!%xRo{fKY#@ z57f2B^0FT+J8n5_x7><g53Q&%%xmkxY3H=eAU$GdH2yafI|a7U3VBlPBd}lZ18k$Y zS$tPw4!KRbpHRM?JM%-Y<)Z<U5m_2_#?Dyg_d-mXj67}?S=(f(k+ty4`K=Px-Bvph z7drK|8s^7J%X6#f!z8~n=8QQLW5#*0APf=^p);RikmNJC`xUqmeBRD)jP`oXt2CN= zf;wwK*p>wuh{iEh&zw1H1|=n@P%;4!I<g4P`ggq4@OeOC`7l1~+lPf_8zTUSq34mV z<RWpy^`Nrqt$Wp4qq15$hh`Wr)%<b^@1gS;E0gm3>+<^>_+46F-Z=7i07-_$L8J$f z9zuF0S5_ILV5xyo2q}q-v-o;pdJg%_`w(~u?}w3u_eYQx^LhL2B{yt)EvMpERxsL1 z<IWi@1ga{L7{5ImlT5`VEb$RY5Q&c>iNwc{F6K(C`fjD=w|y)%cyDS^(rVM4-&n@u z;3+bmKoS{;k*?;-@GEPlgVI^dTW9o?TWhYk&IAUjsuTuqP<2~XjNQ`Q)Z*#OEbK`z z7hy+`MA%WJptmpwt85U~e6*z7ZnQQa`+BM%LpcV~4CN_2pU;JIb38!*;q{qRRr>F> z@l*w$R$PzcQ!01@X*QQDnl1FAJ=a)gjO#WW6Ir3hz@Jefo>d~A!{>Ug2));G!zQLs zydzj^uQXcz-RRAmyu4H&$_toYRg+=!ve%9`0bGhv$)uL%l#PH?`~{@O#!=P1wp(@E zZec+4ssLm8C}cC1lN-Qd*gWN9F>RG-K{}Pe;^6r!`R^;;T?R15J%_p5Ovol_$HGlk z%D^1O^OrUQv*p#Ik>%Dl-h9_OWf|gF2I4rLd%XFnCG~DgEyWvYNL@Htq;2gukpX!c z&z~ze4LmI-!C3d|jn;aP7SzQObn{U$ZoD%YTxaoIbkSg3ywffYi%T)87c;2N;rW|| zN1d*BT8l94$}KZQvq<X@w(dQj0sIo4`x=9(7D=l;8-%LV9a?f&yOE{%V@Sp4vTl;9 z*Yu!|1(kI_xamb&PT?+}?v$NN%5uklJkyRZ<M~H@bi4s#fAiXe*ER?~kpce-p4T=$ z$FWsg4jN&!S5q2q&#uQ7E-Ay!`DzByB%TY~Uub$0q&}OG@Y$O0LI&&<o|iY?d@JpC zv+!`d1Y&Nm=?sP$Jm)J%##f250~CglDEc&6XER7H;<=v{MM3GW+Y(&1E@KmftSFcI zYp#qXqUUH&><-jiY-SK7b4WkvW8qLx()K4k6AJfe0x|Xan4vXPtER63D(^~P$8)&x zao=qDbtrhZV7)E6YFzCU;W_X|2G2a6-zkWvfj0w;v@lVeb;b>93u_NoKnq(ei@vNx zU%{vJ;#H*8f}(Hx!Nj-|u0yS^d$+Jd4ogsO)AoV+j$Qau5%oeuJrhwgj!!Ba*A$Kg ze7=#3Bk;;~>}W;xb<QMupjOvLOP6Or080!ErZjA?D?y7&(3|*tIag4#QLC-P@^My+ z5q0ybWHaYaDb7EwIK%AU!$c5q#=viRY4&3Oh}mb+e>x-MZ9M09S9seESITRZ)f!CC zTIXze)kdXs))8xjn*Abmfv8E>yyi_eDqPpkC>)=~rwp5Skn(j*3l3y2FT>FMqBD^m zI2I(x7JA0=GYZQMd<qs9DSs1?k7d~lHVOykVn$R30$E>fAPJ%hQvUjqk7&)`D4@kT z##4oS#^d4nv$<nFp43jY<zkXAV~E!4ji7sW$Ftd)R%BBV8B1PLl9Oq@jOWS#C1X9k z<+eQsi%Yd}TkNjs*`AfZ0{NG6<*&7uVE0`O8n?y7Bom#c$6|YFG5(D!>8ncK#HV!k zD$-lILhgd61M|<R`dYi@H=&;|HClBnW^&x8<mshstd!8R)S8l7S5gCf%Ae>QTlw;6 zR?9M$<d0t-n^;4MBd?+P=GB60C-LUBbKBNTlFw`Awyu?gf!E4=utpLRUL&`CZ6qAL zHg4gXND_HXl38>g(j$7GM6cb+W>W~{PfTtj>BAPu-V>jV$kziGvga3)yI&tQ$m4Ky z4j|tiicbX6R{8h`gKL5OunPuu`5eXq70N4Q=qWR5HcSiIVIx^$E*6UCHAN5ifeb%` zVO1zz(EJP(4l7qe{%jBO#KA&npUbD!22&ws99k2>7JS+ZaX1tm<~lf9z2&upoSK0Y zm&BBqMg24((aaO-*zhP^sh5~5+r5O0P8n=uT@7ae%&eW*Dkc)R=`>)2y+?@87;9%B z?gDkxTvFRZg#D>rL|tqjHI@)_hVXmB_^Z<9g}~fYCUxJ=PN}#;aM~!ZhYl<Sj~EMT zy;NupXd0z5N-kBgTbC1p;Yj0(zD3%LeA}<}7<qgk<3}KQCkh?v3+2Iuu9J4je!M%| zSyV^&^83SPT|$0(!u*pHDLg{(q=f+4qK62{v3wHf210c-pK4LeCqsvf8*2IGgNxT^ zQK@KOD$frmTf+1fQhpK>(PJk6u-Kh!7(X|V<h3J1cNfynB1v6Bq&CtT(k&z!wESA) z1zwpx60XmqlyH^9M-$iE$RE|itH)|DC+F}uOPKFg%;nj{{0{QXvtOoZ2B{bx^N_CN z_lGmXRe8uHc*OHXScvC~3C~^RpDZ-GBRj^#Si)GmR)q10h4JT*f2@1dg9}c@PnB^H zE6yqjx1Uqoe%`|E7m#n62(jLoF#1Ks=!S*S=aK)EWt>MdDmFMI>|{eMwG<<Qsrh~h z`7%lm=ju`*7<vY2pnfcO>K#wY1K=$R&q1Uu@G&7t>|>Iz{NQw;h;Iq;X5W&*`jn6m z_9@v;UlM|FO!d|geD>)>LfoEK+>Yb3=ou{;*<8aynW*<g9Xkk(tgSK<Wo^w@AnP5g zE1~J%p@T56Lq|~zuncFtV0#$M4L+}gfgL$^iUSA1V+W4nhrQix<P6AhgP@Jk2<qo( zD{+Q!$#<nkSL%&^qM5KB;}EzB0(Z|GH3$zoY80LPo~~}n6QXz{rF17k!wwod&oP65 zvSUVHV=UDIgK)?oaO{v#d~WJaHF!A`?pl-XO3*$|!+&QxU=V0_z{s~J>Q@rY5%O{< z-i%t2=hfp?YQCN2a6!#=fk1AHqXo$peJ9yHY@>q(A($lu{j3awcdQ_AqBU=#d6XAJ z;ZdAeY6t;4RP1C&3WCgz6!}Jc<MiL00|nt=2a2sc`y!Ei_LYB{q^pjz)Xh0eko-@o z@p=uPgL9N10qiKTiNihOIPk+g;(bpJ_elOdJKQ4-_xNy+kd)Qnyn)aBbs;2&6Vg?Y zE#}p}93Kcrm2hm~;T}ok!@Vtcbg;-BjDrI)_f<2BgM<8uA!4BBHuz`li8!0X#=d~x z-bMN%(wC5a8R^SNn++#RWF7)9dG~WTnMvlafMyF!fK+=n0g5ZhV@M_>UqSvrOoSwx zO@t!*utoN-B7d+ZL;}GkL;-Qs0^&X7Z>NcoAhL;3AU<V*_&)OcG(nOyHbIKC6BcQ| zhWr7Aw<Xi!8L*UT@hp;veh$er7{7}Aq6QLL;|F0KrG}D4NzjUQRM5VFWJ3Ej<QKJE z659T)r-YHMr-JdM1>@H@fN>z!Rl>;DRl(?3Fn$C1JH`4+@Y(t*@JB81e;xV7EkIjm zoh9gOofYWg7U<tZen0y2;H|d=egdF)`boH*Y5IK&`S;AaOL*D3D~&B-cqY8Rf&8M2 z8(FpnVf`hvZ2c9q=PYQyjr^Tw9j1z~byzC$lBFWwL4IFjeBjn&0?*cC0sk=z`0paW z_}a2{)@6du)@6bIvIYA0kiWC7&r~V4K1-!uu~h0ek$(@Y(}eU@AmG(;(t`B2kiWC6 z*HnoMnMzDqD)D{fZ;N%Cu(5SphQf>m+YgYxjn;2MI19-<1TI<-{t)^7tbBvFjuZHO z8Ss}Z;D3btKAN{*>p3CZpFuchLHJ|j?_}#bRq3@%m0q`0=_km~x3XyyG;L8fXMHD} zY<-uxH*dlD+sNNa>pVeX>%2g^YJv1S$lo^WJ>g^Pz2IB0;QL+V56-$z0NA=O0N%6! z_&wxr!un4V+4?UM-?B*jedOnxwhD&+kZe935ZP~AWd8y3^M`}QUfpLnWnyXJ(*wcx zjs@QzB0t}DVZo>D=^G6rlz6iQBcCn^M%RMzkC30g*~-V*fB0pBv<w9DroDkANGnKR zKzbL6O~`+Y=lpduA8B8w6V#$r$Y-1$o=rIa1o>NddO?Vm6e2lPl$KjYG9mg?<Zt=u zMh>nO3$8yy{$Nf&NVpHC(oHv!q)}FpOpWsA$bT!>RoU#bUo7l18Qe8U5j1rqK@%XE z(EJ7R??bwii%4$_tY_1$H5k9@xZ?m6iSw#Njn8*{OuM5&WZe*DS^l!Gy1zu839#h< z71CcL{SDIJB8e?`%yCNd9z2CEv=HHo(C;|P*p%l~!e*;+Q<_Kq{tijL|2@(PaCyx` z#Qu^WIMZ^iS?VcM@*k*V1tr5)B~4#;aZ_B1gXB1K^=1usAq4H{!mY43MMmyFD!KoJ z&of!MGaW;L=Bx8Eoc@_=a3#wrYR;LU(vWL69o*g+-B3$#Ed&`E|3YX>kdf-8dE9_m zGh}6W{43!+1|Bo@^15@`t;-FK^gUDR->4M64*fgQ$mO+*Ui)f%pYmMwAyWj*gG}=# zZdk^3vBS|N%M|I-BPL}}9FtNsW^hk@H3;#iK1<zIq9WzYl%_y23dka!%CjgNX|*T5 z+_hM+)t>kY*1W)~eVsc|GhwO2(H)`pUv=9nbAjrP!z!j-){S%!YqZ0NgII^7i$4!3 z(&$Fc&c&bI!L7s5{hdddIvf8gyea+_9gS-^A0;R{HdaXE*w_zeZ_|8270~hcbepCz ziuR+{66t7L5h_8os0wa*JV;ehS6Rl9wis~eyYX%ubR#9jcurcbh#Xh^iv}J5Tz=s0 zsh$jz5MQY%@iK(uT6h^Kg%^j_8bSrw!TcpNNE2f&CQ>+D)gVPj`kpH;Ll+VW=BaM- zlRs|}(Rn2}rcxrKNR;qIVea}mLmF|k_@ovz%ZRg)3tHM$Sjo7bUg?cIbs)fHMP7k+ zO_kSI{PF0}y<qV1wvCS5LV~To2<VxfEn>i!3U(IR<|s<6m&u)XqrJswV&S;4fa{kx z#6H`r_E7ETd-6^0r!&7?)ob`<Pd3T>y3iO)h_m(REhcdzhM2x0!l!#yB$1Gs*)%sH zxmQSeqBUgRJj6tYpUY6`4nN0!L~b{GPNC&ju~tLqb~Ea{ZslY+x}+?5Nf%LN;=uO3 z>aMY41{CSL#)hMycWi`kW!OWCI(20jMa3ukk?uRcEFrmZZ2E9?byre}B3F0yh-4p* zZs1DZ!_q1X<eEvF3S=iYaP^LSPnL<^{L~d%J;UUa!PJ{$d<T{}r2e?-m557pC7t0C zs~5V7Fi?&6O>uZWwsYug?$`9SQI^pC;pq0KBVB_lef!gH=tkaNrMf&g7mCK|SNwKu zecFR+>s5zm-CF2<;F=dUYPY=V?(3~JZ?|m5U(7WG*#G@ZzEhbtAds(=$+K%dSG;Be zrL@5RpvgB$Yi^!?%dY4v048#=z{X(suNZUrWj64SEey9<jHCniKanr1>mOBQ;{Gl7 zUF7>BnoXUt!8qT~)?5<NtDPGL_f3YO*S@~q^!6%i(mwpVjCK6S2)DMojRATP_u;ht zAhMb)td(WB9_-c=_v?QIaj5IEBYAO*q!qe-oqu}pi0QH;DX~{+TKh2V;=BEy(JvHQ z^d>oyTM8aClCi==6eev0JRHr#MA5Yy$R|&$7Uu1iO-W#5hMqAAwchko<IvkXwKb5& znINRZF13@+*3JSrRYvc=PZ}kdcD8_XzA$kX)Nymk^v-syCsCFy>%zKylAWh{@15)s zQ6F<tB?MVvW_HQ84(!eu@h1eS&ZSOAL9=R^-pd`*%xQl{n7a38(M(QnsP@IT2tnqE zzE4Q#Y}<v-g{8LZNN5*x0^d7Vq|%&MBx+-FJfAAwY&>ebzej)`QNP$W5F_b5!iSQh z0x5g0UlaUC(#z_!-@@JT_%8;J*ccwT790ELaMyg>U-G@yE?E5^=oq?V)B9d*<i}vM py1$bz2K8g9CzClmDu-RpgB?meo{o0%EVkznO(2$KjVU%3|39WsNU;C_ literal 0 HcmV?d00001 diff --git a/Jellyfin.Api/obj/Debug/net10.0/Jellyfin.Api.csproj.AssemblyReference.cache b/Jellyfin.Api/obj/Debug/net10.0/Jellyfin.Api.csproj.AssemblyReference.cache new file mode 100644 index 0000000000000000000000000000000000000000..15749218f2a3901b19887fc2e8e5f1865453dc5c GIT binary patch literal 9571 zcmds-X<QRm7l#><MXI0`S3nV$3Wdp@5EPMB5d}qXt1Oe5350|MvVbUxXk8I1V6|Fz zPztD^C~i~~u+)m8$fCBQg4n9yQWYr`cxNUFLt5W}g-<3wGRfR?nf%Xl?z#8ei9Rq= zM@I+np@d4pQA%`}M2yNPpjZ)t$|(^LEQCU^&yXxe3=7d16cI^aA;lh~f^>>Vz@vy! zITxf-obh%qynQy-4k98E0AT-BhyHS4LTIfa0073CCV(N{j6M1(4?2W7_8y4CUiz=b z-e5-^U_bskL3w5y_EsOa$3FYccc$s|$E^UJ_y={7ZN+ONfz?&J3Y=s%Npg=9mUsW; z{SbdHt+?EdU3!FF`xso<?6oXs&&WZqk-4#37N2oV_|@SE_>viUplM1!+f(M(hMBQ8 z7>1PW;7&_1eD>!Jy5+_Jamni*-8f<9(QIY-ruNj>8iQ4XcILDWbt%^`TX|Hmp(Xg- zZeJ&12BGDrdrwTQ&zVqwd746PEf=6tnLr|TB%B6PZ7p3DBDq3}I*L(+TndS7E$1qD zA_49`KZy_(JHl)PLAf+0oy&u$d^#U@0sa7-v@+r$0x@4gp@P_j)NI(;TNgJ^>^#XN z?&d9T*WYj^`pPE4(`p{dODSJ9S*VCODm#(7hzs^amslYZssBCTuU(5k&WB`jR0_fn z94ZipQ0O2FWNOF~$UT%Mu1XV9A;;W#cm6$G;SiU9S^;<f>0y6Z@DPt)1m4?4p0uy} zRZXlk0G>D8ewMa3Y+jVxA3073<~ct-t(O#aVRaI_{CVOk1G6PZI6nru{}dA$JZbg2 z!lLjkdkZoHZcXua8j)U<08C1He0=cHRpa!tziBgHQEy61@r`Y0=5hl)vJKIZf5b<K z#7~}We>uq^&3m42`I5%wg_Ur7Iqw%D@X%smNs_m{F5vestt2DDdp%*qX2A?L4`$k< zOq5|yWvHB_1I*CM3lTuDR3I0?G71x<k)ZJ0;MUnj5<0xn;f*hm7mUbe07ru3P^0O* zxJFJ;#i29Vk<>|CyeB#}VsWlkZ30-q6jRt#Oc&J-5=DeUAcMtLhoVdli4jPO&@rW+ zuKrAKQe;drbz{vPJPh@S7GSDckm}fr(7QPq2>w1V*5yjpMAr+J=}@J$-&=sUHsz36 zmDDdc<C4Xypn!P){HrosbDd>jWs}$R%iU%&e)+5&+ctCZBRNAMyAi(>oBUNUc-eSn zhTZk?lS)HF17qv5=d-HKtsYy=TJgMUbKbRc<4-28I)7p5wt}sT#7W0kN#@A`h7|T+ zP#qC^FOJ71`50T90iJ|D5(DNe;@N$!L-{-!2gag}Petim8c*#ZK-@hO5X+uonMi=8 z2}mxHegq;vjgBO-Td(OVVx~%#nueb)z(pUD+owFaT6u=i#aM>NVn#iz@QG^{y~|)S z86Zqo4klgfJdwrKv4S6aF_iX<s!4I*k}1x-vU%pFDWSUkK5t4i-TvKOq@gmrj9Yz? zN&k6^on8M2Ta8Cm0ruJ17iG`iiz>$K`*ZRHApNB2<10UB)-=zoSa<2bo3tkjMhC_B z`@?e6L+eq|2ae2_{Z+Nc%FrvFUFH}knlWOWUrFKbytdr-#-fR(vj<Rr^?q2fpBPH7 zx6P>DktxUlJTP^n;|ZViB%kpaTqcu)u(=$F%jQDln!!V>-?1f}48>A5mfSkmb7V%N z;WT6@IX;S_&#_{OiB*qiAu=}Z{(;yZKB=l7A6_{SeMoW2%Yv-#vxG*rmS|NJE`X&H znS?I~QL$VgkKs$9a5P#X6~YoJN}=ItiJ=ZM{1atPX9stpgNM3<E7qZlbW6*rONklF zLTeAeNaX`v)v*_m`P#v{zfWe(^ecJe98w!%zdkAb+55B2#-kr4g=Drg*X{|5bM!qj z{?X>kBY{`HeQR`Kt3}!zo4r%Ael;CEqIA{k%jmtp20gFja`^nzurp55Y=@jDjwZ!L z%!DYPoZRCVWsVJ|i;rb{EYkhDaF4vCE*?33t!_q3@;QS|=6g2SRGcRw^XS<ad+U40 zf&oaYZW61~$2|#J2;p%#T!uZJiy#onrglcpCg8s_1|gmdn;2j@w&s?7JaFeOy-E6G zos3oVZM1p;?Ara)4Fz$}ozSOov9339Aw@+H9u$!1<D<g2vo9GFHd)rHn3$~H9c1TE zHzGtmbwQuTLtqcngd_qLLlFWB4P=A#4@`7jX8|9~);U5za?S-9?1x0XJcJafvLbdl z^s&4(y!YAu+t`z9H=H>1)@NybivO!`Cf%~(?i_R?_RR)P&QAqA#rK)p&m1hwS5(&T zbJTA<wPZws*>u`(Co)3nc1GXca((@*LBk6#<y|;$e1Bivx>qJ07k>y!twvh)atvD2 zvML(fN7@xGTjg}?%LPX=174+-{2tIae3sMb*{I_VGoYh*Z(a$J?VT37ZiAy<x&Z$F z(%OX_;l`e1cm|)vVPZ=YHiN_FQ<?P63@_{<xoClyK?h|q*j_+58ih^iG745xYHmJ} z-*h2iJ)f|4C#*?~5f^7bY+~(VuB`(wK>ZziPt<FD$<<|k-!sqUkDfA8r*2C7sk}z6 zj=cXcc1HcxJ8?n%%T3lr`u~uUlx6*_ZAkg~qf;`B+cSPlvl4|Ra*Sr*(tT$-Qz$O5 zTD<PC{fsf<w~=Kj>CjZ2Gp%T~`Sk}^8(zcIEBnVful3(&_F_Vr^@yMD46Q%p%y6*K zyK|x9OZ%$?(XtgoJFeN?n+F7GlTE_K-HScDUzEWxheu=Z`7lC95QIfj`wf__m8}@% zg<}~QuckqkdL>4#IjIzHcgz6PTzyT`MKhid@Qlc2V(_V#SKY2Yoym@*Sk>3)UaZCn z)KROz2#H7(gKd$rNqKlRGZ#NihGW3f<U|`{J*h{u5UkWL#E)uWnoDV5Ki)P$`-bQ+ z!%_8~K078kIQ)1sDRAJ7TyK$at=r+t7IROzSL$AW7JHt4Gx2=EJ&TP$tXv_p6n-vz z$jlAi&CZ&dXHoHJ6_=hmc=CWVHF2XTw2jbam&fxic^B%nygeP3Y34fI<pgs_)0+GM zIL7>LnuW`ngVk5Fi`xy{^YaA*ony0`4={HVT7FLJC~j$)SO&}_^pQTt_d$CWVD=Cm zUJSlH-=5BbVO%->=w>g9@IOc__cb8k+oDPcnz2QaFd~~jT^w^rk$uTTdqS5*L$Xkw z0v3u;5Rye;cMWhppcF+UFog}`+k{wgh~G`XDb4R<?JSG%M&$%CU(`(klOyQRnSBOL z#Jp^-tpk8h&s~#xF9ue;>+qtGpyN|*P3^X}`^>ksayxziMn8YCk7ZPTHsF-rz}qs9 z-ww*c4gd%Ihm|hzeW$onE?sapFxlpP$kK1@9CmJ$Tq$gcGK!3O6gw?vs&sPjWA^K1 zEz^-r&!k5LXYZN&r&a6IV!kqLs|&M*cQp;4s5|=b&e_(NN<EGWpV!|fqSBY|Sem+G z%rGEGn{1LT;ohDkbQYb>W*{(Dkzt7)rLl;?hL1Px75D#Pi=7S$L8TN9$k3dw|IfF^ zPUH3@->nlvTTvqCkI~)*Fu=YfUT*iny`;}p-{|+!b@{fE=9-o7t*`stA3VJomS2su zY7DrWw{>}Vd+;uUj>_+M9hQx)Z>gP=v13nd#<Xp_*S~o0)#&80yI8zz%iAq3B^MNd zZKK<Eiheo!@>iz+8RocseA62y51a=U8<aFQ(M_s@%I1z88gQ9Yb7N1Lh0Q{{2`%os z)_d%|Oc3UwE9LU7ic|p7E}UdcIJqZ`*>pCGgK(G}CYQ=#amoFCh1NMu)@4L?8Nb+R zoLLAe{#eX2P%%lm<yfSO^f>K;v0i-%d&tl;uKC|Bt&SP1xB9!ZSCVVPwrn{2Wyj*B zWfqRf>rIT;UQGP`rZuvwIPY#8OLY6-AhhAS*I`O^`-|x=lCPfF*Om=j>9)}<tJL4M z{LXH}%)!GP;+t$9+w{*{>~*E;#OsA4<H`$O8tWgrYZT$TBrP+tG4|1*&u{uk-?sfR z+WfD_1nk>$cK!{p<{JS>x7z;$Z^WL7aJhUQmBwJP7*wcpOv`$~TOs&Z-2R><?IW@% z6>UrHiv2zN684j(6q7a8_7yIcxy#-~PO#-Um6Wj~-psGaPY^9aqE5$)!gi&ur#0WK z6>Yc3YfUx$qR^vu{X>JVV*90+4H>*Y_Sr?bZ^5+s)qnq;RqH)D<7s2-g8{oPf4OiX zV&X9FlE2Hn^fBvX&O+hSXY_<nBgaZR+J=b<wu4-*J)dKd$htwmcA;NM{vxl)4}iOq z{vTK~=?pr|<<jUJ9s{Pcn4PQxw6iA4B@mH9Sg{p?Eu6x`CE|ZqS<0#kbW@qR5@saT z`|nTxs4TCBebDmLt*aJ&P%p`^_XQQ)hy3PB1yPV31+h{YkwnY>QD?R5pHNXWQ>prp zeNR~qR!1OqRNvDjhQaCOOBak{KG*~VYPrgP@F0)Ffmkq$P2)0{DAk^;zCJKhs|OWw zKBy22#gb_JE)-s@by<x1%lZDwHZs^3cO*=$QkJ7+b6?(*vTi<E+gb|qrNcLPeCBo< z+M6^geaO3YU!)FiJ4O+pO|7#$bo;j_c-L&NVuhJo&7++)j9Gi9<6G+eRD-9-S8lxK zGgvP@NW$`Ey5H=Oji*zn!R7A<MclY*=C#tp(Cord+Si_6WF5_&n}8HrCpKg^JYD)r zLW<|W?Pe<!LyL+s2})bX#r}Sf9$X2`Aw`l<dVFh&OQmDCcu@|7r6)EOA=A=gtx%R@ z3_z?PunSknMS=)y@0Kr-hGQEC*zZl$e8_DB8bxhSQrdZc!l+`RKrmD{Du0!Pg~dOA a?xI;`tA1kOr0zzlCNAHTg;v)MgZ}{v)8tqH literal 0 HcmV?d00001 diff --git a/Jellyfin.Api/obj/Jellyfin.Api.csproj.nuget.dgspec.json b/Jellyfin.Api/obj/Jellyfin.Api.csproj.nuget.dgspec.json new file mode 100644 index 00000000..9d09411b --- /dev/null +++ b/Jellyfin.Api/obj/Jellyfin.Api.csproj.nuget.dgspec.json @@ -0,0 +1,6213 @@ +{ + "format": 1, + "restore": { + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj": {} + }, + "projects": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj", + "projectName": "Jellyfin.Naming", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj", + "projectName": "Jellyfin.Api", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.AspNetCore.Authorization": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Http": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "Swashbuckle.AspNetCore": { + "target": "Package", + "version": "[7.3.2, )", + "versionCentrallyManaged": true + }, + "Swashbuckle.AspNetCore.ReDoc": { + "target": "Package", + "version": "[6.9.0, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "projectName": "Jellyfin.Data", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Logging": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj", + "projectName": "Jellyfin.Common", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.AspNetCore": "(,10.0.32767]", + "Microsoft.AspNetCore.Antiforgery": "(,10.0.32767]", + "Microsoft.AspNetCore.App": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.BearerToken": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Cookies": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.OAuth": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization.Policy": "(,10.0.32767]", + "Microsoft.AspNetCore.Components": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Endpoints": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Forms": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Server": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Web": "(,10.0.32767]", + "Microsoft.AspNetCore.Connections.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.CookiePolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.Internal": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.AspNetCore.HostFiltering": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Html.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Features": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Results": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpLogging": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpOverrides": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpsPolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Identity": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Metadata": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ApiExplorer": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.DataAnnotations": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Xml": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.RazorPages": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.TagHelpers": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ViewFeatures": "(,10.0.32767]", + "Microsoft.AspNetCore.OutputCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.RateLimiting": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor.Runtime": "(,10.0.32767]", + "Microsoft.AspNetCore.RequestDecompression": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCompression": "(,10.0.32767]", + "Microsoft.AspNetCore.Rewrite": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.HttpSys": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IIS": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IISIntegration": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets": "(,10.0.32767]", + "Microsoft.AspNetCore.Session": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticAssets": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticFiles": "(,10.0.32767]", + "Microsoft.AspNetCore.WebSockets": "(,10.0.32767]", + "Microsoft.AspNetCore.WebUtilities": "(,10.0.32767]", + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.Extensions.Caching.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Caching.Memory": "(,10.0.32767]", + "Microsoft.Extensions.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Binder": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.CommandLine": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.FileExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Ini": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Json": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.KeyPerFile": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.UserSecrets": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Xml": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Features": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Composite": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Physical": "(,10.0.32767]", + "Microsoft.Extensions.FileSystemGlobbing": "(,10.0.32767]", + "Microsoft.Extensions.Hosting": "(,10.0.32767]", + "Microsoft.Extensions.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Http": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Core": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Stores": "(,10.0.32767]", + "Microsoft.Extensions.Localization": "(,10.0.32767]", + "Microsoft.Extensions.Localization.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Console": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Debug": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventLog": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventSource": "(,10.0.32767]", + "Microsoft.Extensions.Logging.TraceSource": "(,10.0.32767]", + "Microsoft.Extensions.ObjectPool": "(,10.0.32767]", + "Microsoft.Extensions.Options": "(,10.0.32767]", + "Microsoft.Extensions.Options.ConfigurationExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Options.DataAnnotations": "(,10.0.32767]", + "Microsoft.Extensions.Primitives": "(,10.0.32767]", + "Microsoft.Extensions.Validation": "(,10.0.32767]", + "Microsoft.Extensions.WebEncoders": "(,10.0.32767]", + "Microsoft.JSInterop": "(,10.0.32767]", + "Microsoft.Net.Http.Headers": "(,10.0.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.EventLog": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Cbor": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Cryptography.Xml": "(,10.0.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.RateLimiting": "(,10.0.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj", + "projectName": "Jellyfin.Controller", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "BitFaster.Caching": { + "target": "Package", + "version": "[2.5.4, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Configuration.Binder": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj", + "projectName": "MediaBrowser.MediaEncoding", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "AsyncKeyedLock": { + "target": "Package", + "version": "[8.0.2, )", + "versionCentrallyManaged": true + }, + "BDInfo": { + "target": "Package", + "version": "[0.8.0, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Http": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "UTF.Unknown": { + "target": "Package", + "version": "[2.6.0, )", + "versionCentrallyManaged": true + }, + "libse": { + "target": "Package", + "version": "[4.0.12, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj", + "projectName": "Jellyfin.Model", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "MimeTypes": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[2.5.2, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.AspNetCore": "(,10.0.32767]", + "Microsoft.AspNetCore.Antiforgery": "(,10.0.32767]", + "Microsoft.AspNetCore.App": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.BearerToken": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Cookies": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.OAuth": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization.Policy": "(,10.0.32767]", + "Microsoft.AspNetCore.Components": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Endpoints": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Forms": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Server": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Web": "(,10.0.32767]", + "Microsoft.AspNetCore.Connections.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.CookiePolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.Internal": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.AspNetCore.HostFiltering": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Html.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Features": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Results": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpLogging": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpOverrides": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpsPolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Identity": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Metadata": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ApiExplorer": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.DataAnnotations": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Xml": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.RazorPages": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.TagHelpers": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ViewFeatures": "(,10.0.32767]", + "Microsoft.AspNetCore.OutputCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.RateLimiting": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor.Runtime": "(,10.0.32767]", + "Microsoft.AspNetCore.RequestDecompression": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCompression": "(,10.0.32767]", + "Microsoft.AspNetCore.Rewrite": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.HttpSys": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IIS": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IISIntegration": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets": "(,10.0.32767]", + "Microsoft.AspNetCore.Session": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticAssets": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticFiles": "(,10.0.32767]", + "Microsoft.AspNetCore.WebSockets": "(,10.0.32767]", + "Microsoft.AspNetCore.WebUtilities": "(,10.0.32767]", + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.Extensions.Caching.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Caching.Memory": "(,10.0.32767]", + "Microsoft.Extensions.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Binder": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.CommandLine": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.FileExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Ini": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Json": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.KeyPerFile": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.UserSecrets": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Xml": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Features": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Composite": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Physical": "(,10.0.32767]", + "Microsoft.Extensions.FileSystemGlobbing": "(,10.0.32767]", + "Microsoft.Extensions.Hosting": "(,10.0.32767]", + "Microsoft.Extensions.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Http": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Core": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Stores": "(,10.0.32767]", + "Microsoft.Extensions.Localization": "(,10.0.32767]", + "Microsoft.Extensions.Localization.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Console": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Debug": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventLog": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventSource": "(,10.0.32767]", + "Microsoft.Extensions.Logging.TraceSource": "(,10.0.32767]", + "Microsoft.Extensions.ObjectPool": "(,10.0.32767]", + "Microsoft.Extensions.Options": "(,10.0.32767]", + "Microsoft.Extensions.Options.ConfigurationExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Options.DataAnnotations": "(,10.0.32767]", + "Microsoft.Extensions.Primitives": "(,10.0.32767]", + "Microsoft.Extensions.Validation": "(,10.0.32767]", + "Microsoft.Extensions.WebEncoders": "(,10.0.32767]", + "Microsoft.JSInterop": "(,10.0.32767]", + "Microsoft.Net.Http.Headers": "(,10.0.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.EventLog": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Cbor": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Cryptography.Xml": "(,10.0.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.RateLimiting": "(,10.0.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "projectName": "Jellyfin.CodeAnalysis", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "netstandard2.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "netstandard2.0": { + "targetAlias": "netstandard2.0", + "projectReferences": {} + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "netstandard2.0": { + "targetAlias": "netstandard2.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[3.11.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.CSharp": { + "suppressParent": "All", + "target": "Package", + "version": "[5.0.0, )", + "versionCentrallyManaged": true + }, + "NETStandard.Library": { + "suppressParent": "All", + "target": "Package", + "version": "[2.0.3, )", + "autoReferenced": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/RuntimeIdentifierGraph.json" + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "version": "10.11.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "projectName": "Jellyfin.Database.Implementations", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Design": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Relational": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Tools": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Polly": { + "target": "Package", + "version": "[8.6.5, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "projectName": "Jellyfin.Extensions", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "noWarn": [ + "NU5104" + ], + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "Diacritics": { + "target": "Package", + "version": "[4.1.4, )", + "versionCentrallyManaged": true + }, + "ICU4N.Transliterator": { + "target": "Package", + "version": "[60.1.0-alpha.356, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj", + "projectName": "Jellyfin.MediaEncoding.Hls", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj": { + "version": "10.11.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj", + "projectName": "Jellyfin.MediaEncoding.Keyframes", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "NEbml": { + "target": "Package", + "version": "[1.1.0.5, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj", + "projectName": "Jellyfin.Networking", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + } + } +} \ No newline at end of file diff --git a/Jellyfin.Api/obj/Jellyfin.Api.csproj.nuget.g.props b/Jellyfin.Api/obj/Jellyfin.Api.csproj.nuget.g.props new file mode 100644 index 00000000..ea4f0f12 --- /dev/null +++ b/Jellyfin.Api/obj/Jellyfin.Api.csproj.nuget.g.props @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess> + <RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool> + <ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile> + <NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageRoot> + <NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageFolders> + <NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle> + <NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion> + </PropertyGroup> + <ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <SourceRoot Include="/home/wjones/.nuget/packages/" /> + </ItemGroup> + <ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <Import Project="$(NuGetPackageRoot)microsoft.extensions.apidescription.server/6.0.5/build/Microsoft.Extensions.ApiDescription.Server.props" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.apidescription.server/6.0.5/build/Microsoft.Extensions.ApiDescription.Server.props')" /> + <Import Project="$(NuGetPackageRoot)swashbuckle.aspnetcore/7.3.2/build/Swashbuckle.AspNetCore.props" Condition="Exists('$(NuGetPackageRoot)swashbuckle.aspnetcore/7.3.2/build/Swashbuckle.AspNetCore.props')" /> + <Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props')" /> + <Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props')" /> + </ImportGroup> + <PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <PkgMicrosoft_Extensions_ApiDescription_Server Condition=" '$(PkgMicrosoft_Extensions_ApiDescription_Server)' == '' ">/home/wjones/.nuget/packages/microsoft.extensions.apidescription.server/6.0.5</PkgMicrosoft_Extensions_ApiDescription_Server> + <PkgStyleCop_Analyzers_Unstable Condition=" '$(PkgStyleCop_Analyzers_Unstable)' == '' ">/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556</PkgStyleCop_Analyzers_Unstable> + <PkgSmartAnalyzers_MultithreadingAnalyzer Condition=" '$(PkgSmartAnalyzers_MultithreadingAnalyzer)' == '' ">/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31</PkgSmartAnalyzers_MultithreadingAnalyzer> + <PkgSerilogAnalyzer Condition=" '$(PkgSerilogAnalyzer)' == '' ">/home/wjones/.nuget/packages/seriloganalyzer/0.15.0</PkgSerilogAnalyzer> + <PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers Condition=" '$(PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers)' == '' ">/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0</PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers> + <PkgIDisposableAnalyzers Condition=" '$(PkgIDisposableAnalyzers)' == '' ">/home/wjones/.nuget/packages/idisposableanalyzers/4.0.8</PkgIDisposableAnalyzers> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/Jellyfin.Api/obj/Jellyfin.Api.csproj.nuget.g.targets b/Jellyfin.Api/obj/Jellyfin.Api.csproj.nuget.g.targets new file mode 100644 index 00000000..9f18b43d --- /dev/null +++ b/Jellyfin.Api/obj/Jellyfin.Api.csproj.nuget.g.targets @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <Import Project="$(NuGetPackageRoot)microsoft.extensions.apidescription.server/6.0.5/build/Microsoft.Extensions.ApiDescription.Server.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.apidescription.server/6.0.5/build/Microsoft.Extensions.ApiDescription.Server.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.binder/10.0.3/buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.binder/10.0.3/buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets')" /> + </ImportGroup> +</Project> \ No newline at end of file diff --git a/Jellyfin.Api/obj/project.assets.json b/Jellyfin.Api/obj/project.assets.json new file mode 100644 index 00000000..2a35ee3f --- /dev/null +++ b/Jellyfin.Api/obj/project.assets.json @@ -0,0 +1,2992 @@ +{ + "version": 3, + "targets": { + "net10.0": { + "AsyncKeyedLock/8.0.2": { + "type": "package", + "compile": { + "lib/net9.0/AsyncKeyedLock.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/AsyncKeyedLock.dll": { + "related": ".xml" + } + } + }, + "BDInfo/0.8.0": { + "type": "package", + "compile": { + "lib/net6.0/BDInfo.dll": {} + }, + "runtime": { + "lib/net6.0/BDInfo.dll": {} + } + }, + "BitFaster.Caching/2.5.4": { + "type": "package", + "compile": { + "lib/net6.0/BitFaster.Caching.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/BitFaster.Caching.dll": { + "related": ".xml" + } + } + }, + "Diacritics/4.1.4": { + "type": "package", + "compile": { + "lib/net9.0/Diacritics.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Diacritics.dll": { + "related": ".xml" + } + } + }, + "ICU4N/60.1.0-alpha.356": { + "type": "package", + "dependencies": { + "J2N": "2.0.0", + "Microsoft.Extensions.Caching.Memory": "2.0.0" + }, + "compile": { + "lib/netstandard2.0/ICU4N.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/ICU4N.dll": { + "related": ".xml" + } + } + }, + "ICU4N.Transliterator/60.1.0-alpha.356": { + "type": "package", + "dependencies": { + "ICU4N": "60.1.0-alpha.356" + }, + "compile": { + "lib/netstandard2.0/ICU4N.Transliterator.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/ICU4N.Transliterator.dll": { + "related": ".xml" + } + } + }, + "IDisposableAnalyzers/4.0.8": { + "type": "package" + }, + "J2N/2.0.0": { + "type": "package", + "compile": { + "lib/net6.0/J2N.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/J2N.dll": { + "related": ".xml" + } + } + }, + "libse/4.0.12": { + "type": "package", + "dependencies": { + "System.Drawing.Common": "9.0.2", + "UTF.Unknown": "2.5.1", + "zlib.net-mutliplatform": "1.0.8" + }, + "compile": { + "lib/netstandard2.1/libse.dll": {} + }, + "runtime": { + "lib/netstandard2.1/libse.dll": {} + }, + "contentFiles": { + "contentFiles/any/any/_._": { + "buildAction": "None", + "codeLanguage": "any", + "copyToOutput": false + } + } + }, + "Microsoft.AspNetCore.Authorization/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Metadata": "10.0.3", + "Microsoft.Extensions.Diagnostics": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.AspNetCore.Authorization.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.AspNetCore.Authorization.dll": { + "related": ".xml" + } + } + }, + "Microsoft.AspNetCore.Metadata/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.AspNetCore.Metadata.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.AspNetCore.Metadata.dll": { + "related": ".xml" + } + } + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers/4.14.0": { + "type": "package", + "build": { + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props": {}, + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets": {} + } + }, + "Microsoft.EntityFrameworkCore/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "10.0.3", + "Microsoft.EntityFrameworkCore.Analyzers": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props": {} + } + }, + "Microsoft.EntityFrameworkCore.Abstractions/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + } + }, + "Microsoft.EntityFrameworkCore.Analyzers/10.0.3": { + "type": "package" + }, + "Microsoft.EntityFrameworkCore.Relational/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + } + }, + "Microsoft.Extensions.ApiDescription.Server/6.0.5": { + "type": "package", + "build": { + "build/Microsoft.Extensions.ApiDescription.Server.props": {}, + "build/Microsoft.Extensions.ApiDescription.Server.targets": {} + }, + "buildMultiTargeting": { + "buildMultiTargeting/Microsoft.Extensions.ApiDescription.Server.props": {}, + "buildMultiTargeting/Microsoft.Extensions.ApiDescription.Server.targets": {} + } + }, + "Microsoft.Extensions.Caching.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Caching.Memory/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Caching.Memory.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Caching.Memory.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Configuration/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Configuration.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Configuration.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Configuration.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Configuration.Binder/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Configuration.Binder.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Configuration.Binder.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets": {} + } + }, + "Microsoft.Extensions.DependencyInjection/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Diagnostics/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.3", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.3", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Diagnostics.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Diagnostics.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Diagnostics.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Diagnostics.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Http/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Diagnostics": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Http.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Http.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Logging/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Logging.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Logging.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Logging.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets": {} + } + }, + "Microsoft.Extensions.Options/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Options.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Options.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/Microsoft.Extensions.Options.targets": {} + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Primitives/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.Extensions.Primitives.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Primitives.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.OpenApi/1.6.22": { + "type": "package", + "compile": { + "lib/netstandard2.0/Microsoft.OpenApi.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/netstandard2.0/Microsoft.OpenApi.dll": { + "related": ".pdb;.xml" + } + } + }, + "Microsoft.Win32.SystemEvents/9.0.2": { + "type": "package", + "compile": { + "lib/net9.0/Microsoft.Win32.SystemEvents.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Win32.SystemEvents.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + }, + "runtimeTargets": { + "runtimes/win/lib/net9.0/Microsoft.Win32.SystemEvents.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "NEbml/1.1.0.5": { + "type": "package", + "compile": { + "lib/netstandard2.0/NEbml.Core.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/NEbml.Core.dll": { + "related": ".xml" + } + } + }, + "Polly/8.6.5": { + "type": "package", + "dependencies": { + "Polly.Core": "8.6.5" + }, + "compile": { + "lib/net6.0/Polly.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net6.0/Polly.dll": { + "related": ".pdb;.xml" + } + } + }, + "Polly.Core/8.6.5": { + "type": "package", + "compile": { + "lib/net8.0/Polly.Core.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net8.0/Polly.Core.dll": { + "related": ".pdb;.xml" + } + } + }, + "SerilogAnalyzer/0.15.0": { + "type": "package" + }, + "SmartAnalyzers.MultithreadingAnalyzer/1.1.31": { + "type": "package" + }, + "StyleCop.Analyzers/1.2.0-beta.556": { + "type": "package", + "dependencies": { + "StyleCop.Analyzers.Unstable": "1.2.0.556" + } + }, + "StyleCop.Analyzers.Unstable/1.2.0.556": { + "type": "package" + }, + "Swashbuckle.AspNetCore/7.3.2": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.ApiDescription.Server": "6.0.5", + "Swashbuckle.AspNetCore.Swagger": "7.3.2", + "Swashbuckle.AspNetCore.SwaggerGen": "7.3.2", + "Swashbuckle.AspNetCore.SwaggerUI": "7.3.2" + }, + "build": { + "build/Swashbuckle.AspNetCore.props": {} + }, + "buildMultiTargeting": { + "buildMultiTargeting/Swashbuckle.AspNetCore.props": {} + } + }, + "Swashbuckle.AspNetCore.ReDoc/6.9.0": { + "type": "package", + "compile": { + "lib/net8.0/Swashbuckle.AspNetCore.ReDoc.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net8.0/Swashbuckle.AspNetCore.ReDoc.dll": { + "related": ".pdb;.xml" + } + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Swashbuckle.AspNetCore.Swagger/7.3.2": { + "type": "package", + "dependencies": { + "Microsoft.OpenApi": "1.6.22" + }, + "compile": { + "lib/net9.0/Swashbuckle.AspNetCore.Swagger.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net9.0/Swashbuckle.AspNetCore.Swagger.dll": { + "related": ".pdb;.xml" + } + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Swashbuckle.AspNetCore.SwaggerGen/7.3.2": { + "type": "package", + "dependencies": { + "Swashbuckle.AspNetCore.Swagger": "7.3.2" + }, + "compile": { + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll": { + "related": ".pdb;.xml" + } + } + }, + "Swashbuckle.AspNetCore.SwaggerUI/7.3.2": { + "type": "package", + "compile": { + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll": { + "related": ".pdb;.xml" + } + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "System.Drawing.Common/9.0.2": { + "type": "package", + "dependencies": { + "Microsoft.Win32.SystemEvents": "9.0.2" + }, + "compile": { + "lib/net9.0/System.Drawing.Common.dll": { + "related": ".pdb;.xml" + }, + "lib/net9.0/System.Private.Windows.Core.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/System.Drawing.Common.dll": { + "related": ".pdb;.xml" + }, + "lib/net9.0/System.Private.Windows.Core.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "UTF.Unknown/2.6.0": { + "type": "package", + "compile": { + "lib/net8.0/UtfUnknown.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/UtfUnknown.dll": { + "related": ".xml" + } + } + }, + "zlib.net-mutliplatform/1.0.8": { + "type": "package", + "compile": { + "lib/netstandard2.1/zlib.net.dll": {} + }, + "runtime": { + "lib/netstandard2.1/zlib.net.dll": {} + } + }, + "Jellyfin.CodeAnalysis/1.0.0": { + "type": "project", + "framework": ".NETStandard,Version=v2.0", + "compile": { + "bin/placeholder/Jellyfin.CodeAnalysis.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.CodeAnalysis.dll": {} + } + }, + "Jellyfin.Common/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Common.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Common.dll": {} + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Jellyfin.Controller/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "BitFaster.Caching": "2.5.4", + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.MediaEncoding.Keyframes": "10.11.0", + "Jellyfin.Model": "10.12.0", + "Jellyfin.Naming": "10.12.0", + "Microsoft.Extensions.Configuration.Binder": "10.0.3" + }, + "compile": { + "bin/placeholder/Jellyfin.Controller.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Controller.dll": {} + } + }, + "Jellyfin.Data/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Database.Implementations": "10.11.0", + "Microsoft.Extensions.Logging": "10.0.3" + }, + "compile": { + "bin/placeholder/Jellyfin.Data.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Data.dll": {} + } + }, + "Jellyfin.Database.Implementations/10.11.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Polly": "8.6.5" + }, + "compile": { + "bin/placeholder/Jellyfin.Database.Implementations.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Database.Implementations.dll": {} + } + }, + "Jellyfin.Extensions/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Diacritics": "4.1.4", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "Jellyfin.CodeAnalysis": "1.0.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Extensions.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Extensions.dll": {} + } + }, + "Jellyfin.MediaEncoding.Hls/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.MediaEncoding.Keyframes": "10.11.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.MediaEncoding.Hls.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.MediaEncoding.Hls.dll": {} + } + }, + "Jellyfin.MediaEncoding.Keyframes/10.11.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "NEbml": "1.1.0.5" + }, + "compile": { + "bin/placeholder/Jellyfin.MediaEncoding.Keyframes.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.MediaEncoding.Keyframes.dll": {} + } + }, + "Jellyfin.Model/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Data": "10.12.0", + "Jellyfin.Extensions": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Model.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Model.dll": {} + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Jellyfin.Naming/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Naming.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Naming.dll": {} + } + }, + "Jellyfin.Networking/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Controller": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Networking.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Networking.dll": {} + } + }, + "MediaBrowser.MediaEncoding/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "AsyncKeyedLock": "8.0.2", + "BDInfo": "0.8.0", + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.Model": "10.12.0", + "Microsoft.Extensions.Http": "10.0.3", + "UTF.Unknown": "2.6.0", + "libse": "4.0.12" + }, + "compile": { + "bin/placeholder/MediaBrowser.MediaEncoding.dll": {} + }, + "runtime": { + "bin/placeholder/MediaBrowser.MediaEncoding.dll": {} + } + } + } + }, + "libraries": { + "AsyncKeyedLock/8.0.2": { + "sha512": "QGys5cnIerNryv7V14PDkvGnlLz69kJtTfdnr+Lndcu+lRre397RNyU4FIeAJWgI9u73lTzXL52Qca9B/ncLXw==", + "type": "package", + "path": "asynckeyedlock/8.0.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "asynckeyedlock.8.0.2.nupkg.sha512", + "asynckeyedlock.nuspec", + "lib/net5.0/AsyncKeyedLock.dll", + "lib/net5.0/AsyncKeyedLock.xml", + "lib/net6.0/AsyncKeyedLock.dll", + "lib/net6.0/AsyncKeyedLock.xml", + "lib/net7.0/AsyncKeyedLock.dll", + "lib/net7.0/AsyncKeyedLock.xml", + "lib/net8.0/AsyncKeyedLock.dll", + "lib/net8.0/AsyncKeyedLock.xml", + "lib/net9.0/AsyncKeyedLock.dll", + "lib/net9.0/AsyncKeyedLock.xml", + "lib/netstandard2.0/AsyncKeyedLock.dll", + "lib/netstandard2.0/AsyncKeyedLock.xml", + "lib/netstandard2.1/AsyncKeyedLock.dll", + "lib/netstandard2.1/AsyncKeyedLock.xml", + "logo.png" + ] + }, + "BDInfo/0.8.0": { + "sha512": "9pz06aM0ceknGQ410IqqO0176ZPp2JPX00ekDDICaiU5sVh2qThOIAEfID08QWPS0xoM/JYtVsO42uDVf+08MQ==", + "type": "package", + "path": "bdinfo/0.8.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "bdinfo.0.8.0.nupkg.sha512", + "bdinfo.nuspec", + "lib/net6.0/BDInfo.dll", + "lib/netstandard2.1/BDInfo.dll" + ] + }, + "BitFaster.Caching/2.5.4": { + "sha512": "1QroTY1PVCZOSG9FnkkCrmCKk/+bZCgI/YXq376HnYwUDJ4Ho0EaV4YaA/5v5WYLnwIwIO7RZkdWbg9pxIpueQ==", + "type": "package", + "path": "bitfaster.caching/2.5.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "ReadMe.md", + "bitfaster.caching.2.5.4.nupkg.sha512", + "bitfaster.caching.nuspec", + "lib/net6.0/BitFaster.Caching.dll", + "lib/net6.0/BitFaster.Caching.xml", + "lib/netcoreapp3.1/BitFaster.Caching.dll", + "lib/netcoreapp3.1/BitFaster.Caching.xml", + "lib/netstandard2.0/BitFaster.Caching.dll", + "lib/netstandard2.0/BitFaster.Caching.xml" + ] + }, + "Diacritics/4.1.4": { + "sha512": "9JK2bI+oWnzOK4Lu+5FXt7czofD28AMnZ95np5fkSClqioPnga1JVpjaUxVJaEpc/5HIlfikc6YI33lm2qu4mw==", + "type": "package", + "path": "diacritics/4.1.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "README.md", + "diacritics.4.1.4.nupkg.sha512", + "diacritics.nuspec", + "lib/net48/Diacritics.dll", + "lib/net48/Diacritics.xml", + "lib/net7.0/Diacritics.dll", + "lib/net7.0/Diacritics.xml", + "lib/net8.0/Diacritics.dll", + "lib/net8.0/Diacritics.xml", + "lib/net9.0/Diacritics.dll", + "lib/net9.0/Diacritics.xml", + "lib/netstandard1.2/Diacritics.dll", + "lib/netstandard1.2/Diacritics.xml", + "lib/netstandard2.0/Diacritics.dll", + "lib/netstandard2.0/Diacritics.xml", + "lib/netstandard2.1/Diacritics.dll", + "lib/netstandard2.1/Diacritics.xml", + "logo.png" + ] + }, + "ICU4N/60.1.0-alpha.356": { + "sha512": "YMZtDnjcqWzziOKiE7w6Ma7Rl5vuFDxzOsUlHh1QyfghbNEIZQOLRs9MMfwCWAjX6n9UitrF6vLXy55Z5q+4Fg==", + "type": "package", + "path": "icu4n/60.1.0-alpha.356", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "icu4n.60.1.0-alpha.356.nupkg.sha512", + "icu4n.nuspec", + "lib/net40/ICU4N.dll", + "lib/net40/ICU4N.xml", + "lib/net451/ICU4N.dll", + "lib/net451/ICU4N.xml", + "lib/netstandard2.0/ICU4N.dll", + "lib/netstandard2.0/ICU4N.xml", + "unicode-icon-128x128.jpg" + ] + }, + "ICU4N.Transliterator/60.1.0-alpha.356": { + "sha512": "lFOSO6bbEtB6HkWMNDJAq+rFwVyi9g6xVc5O/2xHa6iZnV7wLVDqCbaQ4W4vIeBSQZAafqhxciaEkmAvSdzlCg==", + "type": "package", + "path": "icu4n.transliterator/60.1.0-alpha.356", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "icu4n.transliterator.60.1.0-alpha.356.nupkg.sha512", + "icu4n.transliterator.nuspec", + "lib/net40/ICU4N.Transliterator.dll", + "lib/net40/ICU4N.Transliterator.xml", + "lib/net451/ICU4N.Transliterator.dll", + "lib/net451/ICU4N.Transliterator.xml", + "lib/netstandard2.0/ICU4N.Transliterator.dll", + "lib/netstandard2.0/ICU4N.Transliterator.xml", + "unicode-icon-128x128.jpg" + ] + }, + "IDisposableAnalyzers/4.0.8": { + "sha512": "vNi4NMG0CcJyjXxiNDcQ21FwV/whM9o9OEZKD+oP7tuxAqFEzX/x5OhC3OZJqW/w+8GOtCmJPBquYgMWgz0rfQ==", + "type": "package", + "path": "idisposableanalyzers/4.0.8", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/IDisposableAnalyzers.dll", + "idisposableanalyzers.4.0.8.nupkg.sha512", + "idisposableanalyzers.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "J2N/2.0.0": { + "sha512": "M5bwDajAARZiyqupU+rHQJnsVLxNBOHJ8vKYHd8LcLIb1FgLfzzcJvc31Qo5Xz/GEHFjDF9ScjKL/ks/zRTXuA==", + "type": "package", + "path": "j2n/2.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "j2n-icon-100x100.png", + "j2n.2.0.0.nupkg.sha512", + "j2n.nuspec", + "lib/net40/J2N.dll", + "lib/net40/J2N.xml", + "lib/net45/J2N.dll", + "lib/net45/J2N.xml", + "lib/net5.0/J2N.dll", + "lib/net5.0/J2N.xml", + "lib/net6.0/J2N.dll", + "lib/net6.0/J2N.xml", + "lib/netcoreapp3.0/J2N.dll", + "lib/netcoreapp3.0/J2N.xml", + "lib/netstandard2.0/J2N.dll", + "lib/netstandard2.0/J2N.xml", + "lib/netstandard2.1/J2N.dll", + "lib/netstandard2.1/J2N.xml", + "readme.md" + ] + }, + "libse/4.0.12": { + "sha512": "vI4V5rwg5by28SFZXHPciIinwZ4khaE5Y5SS/rTzKxlqxrdmx2RLUrCReejjRfgtVZfvWFKnODTmr2kQ6Qnl2Q==", + "type": "package", + "path": "libse/4.0.12", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.txt", + "Readme.md", + "content/Icon.png", + "contentFiles/any/net48/Icon.png", + "contentFiles/any/netstandard2.1/Icon.png", + "lib/net48/libse.dll", + "lib/netstandard2.1/libse.dll", + "libse.4.0.12.nupkg.sha512", + "libse.nuspec" + ] + }, + "Microsoft.AspNetCore.Authorization/10.0.3": { + "sha512": "pqm2ivtD2bj5f+4KnrGmJsD/iDZkMnJnK/uW/p1bpqKCR316TyWqyhhS5znLGw7QpX2fAWhXU+uQo1Cb89bedA==", + "type": "package", + "path": "microsoft.aspnetcore.authorization/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "THIRD-PARTY-NOTICES.TXT", + "lib/net10.0/Microsoft.AspNetCore.Authorization.dll", + "lib/net10.0/Microsoft.AspNetCore.Authorization.xml", + "lib/net462/Microsoft.AspNetCore.Authorization.dll", + "lib/net462/Microsoft.AspNetCore.Authorization.xml", + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.xml", + "microsoft.aspnetcore.authorization.10.0.3.nupkg.sha512", + "microsoft.aspnetcore.authorization.nuspec" + ] + }, + "Microsoft.AspNetCore.Metadata/10.0.3": { + "sha512": "PtLHFABwDpGhpTMxni8z4W0J2b+y2EVFkpZ8K6A092pbdBdlD3yAgxAZhwLxXl2RKBTuVj5TUGc2voDQ/ghpTA==", + "type": "package", + "path": "microsoft.aspnetcore.metadata/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "THIRD-PARTY-NOTICES.TXT", + "lib/net10.0/Microsoft.AspNetCore.Metadata.dll", + "lib/net10.0/Microsoft.AspNetCore.Metadata.xml", + "lib/net462/Microsoft.AspNetCore.Metadata.dll", + "lib/net462/Microsoft.AspNetCore.Metadata.xml", + "lib/netstandard2.0/Microsoft.AspNetCore.Metadata.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Metadata.xml", + "microsoft.aspnetcore.metadata.10.0.3.nupkg.sha512", + "microsoft.aspnetcore.metadata.nuspec" + ] + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers/4.14.0": { + "sha512": "gSWJlDWwmDhtbrEJGiHqvEjz9KthIiFD0qYB8zZ6a7z+xpMSPEtM9yTYELSa58iFWYlzSRqP9FXO6KoT3+ZMtg==", + "type": "package", + "path": "microsoft.codeanalysis.bannedapianalyzers/4.14.0", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.txt", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.dll", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.CSharp.BannedApiAnalyzers.dll", + "analyzers/dotnet/cs/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/de/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/es/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/fr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/it/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ja/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ko/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/pl/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/pt-BR/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ru/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/tr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hans/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hant/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.BannedApiAnalyzers.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.VisualBasic.BannedApiAnalyzers.dll", + "analyzers/dotnet/vb/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/de/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/es/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/fr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/it/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ja/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ko/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/pl/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/pt-BR/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ru/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/tr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hans/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hant/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props", + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets", + "documentation/Microsoft.CodeAnalysis.BannedApiAnalyzers.md", + "documentation/Microsoft.CodeAnalysis.BannedApiAnalyzers.sarif", + "editorconfig/AllRulesDefault/.editorconfig", + "editorconfig/AllRulesDisabled/.editorconfig", + "editorconfig/AllRulesEnabled/.editorconfig", + "editorconfig/ApiDesignRulesDefault/.editorconfig", + "editorconfig/ApiDesignRulesEnabled/.editorconfig", + "editorconfig/DataflowRulesDefault/.editorconfig", + "editorconfig/DataflowRulesEnabled/.editorconfig", + "editorconfig/PortedFromFxCopRulesDefault/.editorconfig", + "editorconfig/PortedFromFxCopRulesEnabled/.editorconfig", + "microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512", + "microsoft.codeanalysis.bannedapianalyzers.nuspec", + "rulesets/AllRulesDefault.ruleset", + "rulesets/AllRulesDisabled.ruleset", + "rulesets/AllRulesEnabled.ruleset", + "rulesets/ApiDesignRulesDefault.ruleset", + "rulesets/ApiDesignRulesEnabled.ruleset", + "rulesets/DataflowRulesDefault.ruleset", + "rulesets/DataflowRulesEnabled.ruleset", + "rulesets/PortedFromFxCopRulesDefault.ruleset", + "rulesets/PortedFromFxCopRulesEnabled.ruleset", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Microsoft.EntityFrameworkCore/10.0.3": { + "sha512": "CDEImwD4A7BseABJMCpLZnhfFjmPY/bHwhhS70elc6gLI/bYUEOhxWt7PmaNGYGhIEzOnStlCy5QcVb+8dod5Q==", + "type": "package", + "path": "microsoft.entityframeworkcore/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props", + "lib/net10.0/Microsoft.EntityFrameworkCore.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.xml", + "microsoft.entityframeworkcore.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Abstractions/10.0.3": { + "sha512": "c7Uoz381xnMHNBRB8eHRhGgzUtXbgddlbODhwZRrTSzZWDharp3RkJsFwhxyESbeXhCqmML7VdvjMQ7uu+HreA==", + "type": "package", + "path": "microsoft.entityframeworkcore.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.xml", + "microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.abstractions.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Analyzers/10.0.3": { + "sha512": "lVABgJTyTUNE7Bi0bSu4dWHiCHIXEGzTh/kLh0N07IgU/tIKwTeBPp8tgV4x2iKj4h7iPLo8oXzyHmLDGtAE1g==", + "type": "package", + "path": "microsoft.entityframeworkcore.analyzers/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "analyzers/dotnet/cs/Microsoft.EntityFrameworkCore.Analyzers.dll", + "docs/PACKAGE.md", + "microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.analyzers.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Relational/10.0.3": { + "sha512": "Pmh60OK9neVr/M0FJwm9hlzm2bD4Kd65SID8E6SP5c90tExNgXwORrlEWl0oGU/ig9ifpNN4PSpIrnHNozlT5w==", + "type": "package", + "path": "microsoft.entityframeworkcore.relational/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.xml", + "microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.relational.nuspec" + ] + }, + "Microsoft.Extensions.ApiDescription.Server/6.0.5": { + "sha512": "Ckb5EDBUNJdFWyajfXzUIMRkhf52fHZOQuuZg/oiu8y7zDCVwD0iHhew6MnThjHmevanpxL3f5ci2TtHQEN6bw==", + "type": "package", + "path": "microsoft.extensions.apidescription.server/6.0.5", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "build/Microsoft.Extensions.ApiDescription.Server.props", + "build/Microsoft.Extensions.ApiDescription.Server.targets", + "buildMultiTargeting/Microsoft.Extensions.ApiDescription.Server.props", + "buildMultiTargeting/Microsoft.Extensions.ApiDescription.Server.targets", + "microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512", + "microsoft.extensions.apidescription.server.nuspec", + "tools/Newtonsoft.Json.dll", + "tools/dotnet-getdocument.deps.json", + "tools/dotnet-getdocument.dll", + "tools/dotnet-getdocument.runtimeconfig.json", + "tools/net461-x86/GetDocument.Insider.exe", + "tools/net461-x86/GetDocument.Insider.exe.config", + "tools/net461-x86/Microsoft.Win32.Primitives.dll", + "tools/net461-x86/System.AppContext.dll", + "tools/net461-x86/System.Buffers.dll", + "tools/net461-x86/System.Collections.Concurrent.dll", + "tools/net461-x86/System.Collections.NonGeneric.dll", + "tools/net461-x86/System.Collections.Specialized.dll", + "tools/net461-x86/System.Collections.dll", + "tools/net461-x86/System.ComponentModel.EventBasedAsync.dll", + "tools/net461-x86/System.ComponentModel.Primitives.dll", + "tools/net461-x86/System.ComponentModel.TypeConverter.dll", + "tools/net461-x86/System.ComponentModel.dll", + "tools/net461-x86/System.Console.dll", + "tools/net461-x86/System.Data.Common.dll", + "tools/net461-x86/System.Diagnostics.Contracts.dll", + "tools/net461-x86/System.Diagnostics.Debug.dll", + "tools/net461-x86/System.Diagnostics.DiagnosticSource.dll", + "tools/net461-x86/System.Diagnostics.FileVersionInfo.dll", + "tools/net461-x86/System.Diagnostics.Process.dll", + "tools/net461-x86/System.Diagnostics.StackTrace.dll", + "tools/net461-x86/System.Diagnostics.TextWriterTraceListener.dll", + "tools/net461-x86/System.Diagnostics.Tools.dll", + "tools/net461-x86/System.Diagnostics.TraceSource.dll", + "tools/net461-x86/System.Diagnostics.Tracing.dll", + "tools/net461-x86/System.Drawing.Primitives.dll", + "tools/net461-x86/System.Dynamic.Runtime.dll", + "tools/net461-x86/System.Globalization.Calendars.dll", + "tools/net461-x86/System.Globalization.Extensions.dll", + "tools/net461-x86/System.Globalization.dll", + "tools/net461-x86/System.IO.Compression.ZipFile.dll", + "tools/net461-x86/System.IO.Compression.dll", + "tools/net461-x86/System.IO.FileSystem.DriveInfo.dll", + "tools/net461-x86/System.IO.FileSystem.Primitives.dll", + "tools/net461-x86/System.IO.FileSystem.Watcher.dll", + "tools/net461-x86/System.IO.FileSystem.dll", + "tools/net461-x86/System.IO.IsolatedStorage.dll", + "tools/net461-x86/System.IO.MemoryMappedFiles.dll", + "tools/net461-x86/System.IO.Pipes.dll", + "tools/net461-x86/System.IO.UnmanagedMemoryStream.dll", + "tools/net461-x86/System.IO.dll", + "tools/net461-x86/System.Linq.Expressions.dll", + "tools/net461-x86/System.Linq.Parallel.dll", + "tools/net461-x86/System.Linq.Queryable.dll", + "tools/net461-x86/System.Linq.dll", + "tools/net461-x86/System.Memory.dll", + "tools/net461-x86/System.Net.Http.dll", + "tools/net461-x86/System.Net.NameResolution.dll", + "tools/net461-x86/System.Net.NetworkInformation.dll", + "tools/net461-x86/System.Net.Ping.dll", + "tools/net461-x86/System.Net.Primitives.dll", + "tools/net461-x86/System.Net.Requests.dll", + "tools/net461-x86/System.Net.Security.dll", + "tools/net461-x86/System.Net.Sockets.dll", + "tools/net461-x86/System.Net.WebHeaderCollection.dll", + "tools/net461-x86/System.Net.WebSockets.Client.dll", + "tools/net461-x86/System.Net.WebSockets.dll", + "tools/net461-x86/System.Numerics.Vectors.dll", + "tools/net461-x86/System.ObjectModel.dll", + "tools/net461-x86/System.Reflection.Extensions.dll", + "tools/net461-x86/System.Reflection.Primitives.dll", + "tools/net461-x86/System.Reflection.dll", + "tools/net461-x86/System.Resources.Reader.dll", + "tools/net461-x86/System.Resources.ResourceManager.dll", + "tools/net461-x86/System.Resources.Writer.dll", + "tools/net461-x86/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net461-x86/System.Runtime.CompilerServices.VisualC.dll", + "tools/net461-x86/System.Runtime.Extensions.dll", + "tools/net461-x86/System.Runtime.Handles.dll", + "tools/net461-x86/System.Runtime.InteropServices.RuntimeInformation.dll", + "tools/net461-x86/System.Runtime.InteropServices.dll", + "tools/net461-x86/System.Runtime.Numerics.dll", + "tools/net461-x86/System.Runtime.Serialization.Formatters.dll", + "tools/net461-x86/System.Runtime.Serialization.Json.dll", + "tools/net461-x86/System.Runtime.Serialization.Primitives.dll", + "tools/net461-x86/System.Runtime.Serialization.Xml.dll", + "tools/net461-x86/System.Runtime.dll", + "tools/net461-x86/System.Security.Claims.dll", + "tools/net461-x86/System.Security.Cryptography.Algorithms.dll", + "tools/net461-x86/System.Security.Cryptography.Csp.dll", + "tools/net461-x86/System.Security.Cryptography.Encoding.dll", + "tools/net461-x86/System.Security.Cryptography.Primitives.dll", + "tools/net461-x86/System.Security.Cryptography.X509Certificates.dll", + "tools/net461-x86/System.Security.Principal.dll", + "tools/net461-x86/System.Security.SecureString.dll", + "tools/net461-x86/System.Text.Encoding.Extensions.dll", + "tools/net461-x86/System.Text.Encoding.dll", + "tools/net461-x86/System.Text.RegularExpressions.dll", + "tools/net461-x86/System.Threading.Overlapped.dll", + "tools/net461-x86/System.Threading.Tasks.Parallel.dll", + "tools/net461-x86/System.Threading.Tasks.dll", + "tools/net461-x86/System.Threading.Thread.dll", + "tools/net461-x86/System.Threading.ThreadPool.dll", + "tools/net461-x86/System.Threading.Timer.dll", + "tools/net461-x86/System.Threading.dll", + "tools/net461-x86/System.ValueTuple.dll", + "tools/net461-x86/System.Xml.ReaderWriter.dll", + "tools/net461-x86/System.Xml.XDocument.dll", + "tools/net461-x86/System.Xml.XPath.XDocument.dll", + "tools/net461-x86/System.Xml.XPath.dll", + "tools/net461-x86/System.Xml.XmlDocument.dll", + "tools/net461-x86/System.Xml.XmlSerializer.dll", + "tools/net461-x86/netstandard.dll", + "tools/net461/GetDocument.Insider.exe", + "tools/net461/GetDocument.Insider.exe.config", + "tools/net461/Microsoft.Win32.Primitives.dll", + "tools/net461/System.AppContext.dll", + "tools/net461/System.Buffers.dll", + "tools/net461/System.Collections.Concurrent.dll", + "tools/net461/System.Collections.NonGeneric.dll", + "tools/net461/System.Collections.Specialized.dll", + "tools/net461/System.Collections.dll", + "tools/net461/System.ComponentModel.EventBasedAsync.dll", + "tools/net461/System.ComponentModel.Primitives.dll", + "tools/net461/System.ComponentModel.TypeConverter.dll", + "tools/net461/System.ComponentModel.dll", + "tools/net461/System.Console.dll", + "tools/net461/System.Data.Common.dll", + "tools/net461/System.Diagnostics.Contracts.dll", + "tools/net461/System.Diagnostics.Debug.dll", + "tools/net461/System.Diagnostics.DiagnosticSource.dll", + "tools/net461/System.Diagnostics.FileVersionInfo.dll", + "tools/net461/System.Diagnostics.Process.dll", + "tools/net461/System.Diagnostics.StackTrace.dll", + "tools/net461/System.Diagnostics.TextWriterTraceListener.dll", + "tools/net461/System.Diagnostics.Tools.dll", + "tools/net461/System.Diagnostics.TraceSource.dll", + "tools/net461/System.Diagnostics.Tracing.dll", + "tools/net461/System.Drawing.Primitives.dll", + "tools/net461/System.Dynamic.Runtime.dll", + "tools/net461/System.Globalization.Calendars.dll", + "tools/net461/System.Globalization.Extensions.dll", + "tools/net461/System.Globalization.dll", + "tools/net461/System.IO.Compression.ZipFile.dll", + "tools/net461/System.IO.Compression.dll", + "tools/net461/System.IO.FileSystem.DriveInfo.dll", + "tools/net461/System.IO.FileSystem.Primitives.dll", + "tools/net461/System.IO.FileSystem.Watcher.dll", + "tools/net461/System.IO.FileSystem.dll", + "tools/net461/System.IO.IsolatedStorage.dll", + "tools/net461/System.IO.MemoryMappedFiles.dll", + "tools/net461/System.IO.Pipes.dll", + "tools/net461/System.IO.UnmanagedMemoryStream.dll", + "tools/net461/System.IO.dll", + "tools/net461/System.Linq.Expressions.dll", + "tools/net461/System.Linq.Parallel.dll", + "tools/net461/System.Linq.Queryable.dll", + "tools/net461/System.Linq.dll", + "tools/net461/System.Memory.dll", + "tools/net461/System.Net.Http.dll", + "tools/net461/System.Net.NameResolution.dll", + "tools/net461/System.Net.NetworkInformation.dll", + "tools/net461/System.Net.Ping.dll", + "tools/net461/System.Net.Primitives.dll", + "tools/net461/System.Net.Requests.dll", + "tools/net461/System.Net.Security.dll", + "tools/net461/System.Net.Sockets.dll", + "tools/net461/System.Net.WebHeaderCollection.dll", + "tools/net461/System.Net.WebSockets.Client.dll", + "tools/net461/System.Net.WebSockets.dll", + "tools/net461/System.Numerics.Vectors.dll", + "tools/net461/System.ObjectModel.dll", + "tools/net461/System.Reflection.Extensions.dll", + "tools/net461/System.Reflection.Primitives.dll", + "tools/net461/System.Reflection.dll", + "tools/net461/System.Resources.Reader.dll", + "tools/net461/System.Resources.ResourceManager.dll", + "tools/net461/System.Resources.Writer.dll", + "tools/net461/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net461/System.Runtime.CompilerServices.VisualC.dll", + "tools/net461/System.Runtime.Extensions.dll", + "tools/net461/System.Runtime.Handles.dll", + "tools/net461/System.Runtime.InteropServices.RuntimeInformation.dll", + "tools/net461/System.Runtime.InteropServices.dll", + "tools/net461/System.Runtime.Numerics.dll", + "tools/net461/System.Runtime.Serialization.Formatters.dll", + "tools/net461/System.Runtime.Serialization.Json.dll", + "tools/net461/System.Runtime.Serialization.Primitives.dll", + "tools/net461/System.Runtime.Serialization.Xml.dll", + "tools/net461/System.Runtime.dll", + "tools/net461/System.Security.Claims.dll", + "tools/net461/System.Security.Cryptography.Algorithms.dll", + "tools/net461/System.Security.Cryptography.Csp.dll", + "tools/net461/System.Security.Cryptography.Encoding.dll", + "tools/net461/System.Security.Cryptography.Primitives.dll", + "tools/net461/System.Security.Cryptography.X509Certificates.dll", + "tools/net461/System.Security.Principal.dll", + "tools/net461/System.Security.SecureString.dll", + "tools/net461/System.Text.Encoding.Extensions.dll", + "tools/net461/System.Text.Encoding.dll", + "tools/net461/System.Text.RegularExpressions.dll", + "tools/net461/System.Threading.Overlapped.dll", + "tools/net461/System.Threading.Tasks.Parallel.dll", + "tools/net461/System.Threading.Tasks.dll", + "tools/net461/System.Threading.Thread.dll", + "tools/net461/System.Threading.ThreadPool.dll", + "tools/net461/System.Threading.Timer.dll", + "tools/net461/System.Threading.dll", + "tools/net461/System.ValueTuple.dll", + "tools/net461/System.Xml.ReaderWriter.dll", + "tools/net461/System.Xml.XDocument.dll", + "tools/net461/System.Xml.XPath.XDocument.dll", + "tools/net461/System.Xml.XPath.dll", + "tools/net461/System.Xml.XmlDocument.dll", + "tools/net461/System.Xml.XmlSerializer.dll", + "tools/net461/netstandard.dll", + "tools/netcoreapp2.1/GetDocument.Insider.deps.json", + "tools/netcoreapp2.1/GetDocument.Insider.dll", + "tools/netcoreapp2.1/GetDocument.Insider.runtimeconfig.json", + "tools/netcoreapp2.1/System.Diagnostics.DiagnosticSource.dll" + ] + }, + "Microsoft.Extensions.Caching.Abstractions/10.0.3": { + "sha512": "5dtXBvI8t3z8pF4tB38JYgi/enCL/DwSXxpqShgFz3SHJ7IzqFIMs6Gu5ik8sNZzcO9qQs3xIDpB3vDamkYG+Q==", + "type": "package", + "path": "microsoft.extensions.caching.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Caching.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Caching.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.xml", + "microsoft.extensions.caching.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.caching.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Caching.Memory/10.0.3": { + "sha512": "No4fVh0z30SWqiWFRoA4PNdrEco6OjXvCqRFvlmRgDQqqks2bRDdeavUgWEiAX153ZAwW9loUgbxcvuP4NKQLg==", + "type": "package", + "path": "microsoft.extensions.caching.memory/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Caching.Memory.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Caching.Memory.targets", + "lib/net10.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net10.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/net462/Microsoft.Extensions.Caching.Memory.dll", + "lib/net462/Microsoft.Extensions.Caching.Memory.xml", + "lib/net8.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net8.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/net9.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net9.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.xml", + "microsoft.extensions.caching.memory.10.0.3.nupkg.sha512", + "microsoft.extensions.caching.memory.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Configuration/10.0.3": { + "sha512": "H1Cjv2xmm7O3iAGmFTcnSM0ZhLQ/7SqefmAvSJoT1PbXoxeYc2fo0mCLn2JlVbr9E6YpoU9q/o0fI9neDJB0xQ==", + "type": "package", + "path": "microsoft.extensions.configuration/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Configuration.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Configuration.targets", + "lib/net10.0/Microsoft.Extensions.Configuration.dll", + "lib/net10.0/Microsoft.Extensions.Configuration.xml", + "lib/net462/Microsoft.Extensions.Configuration.dll", + "lib/net462/Microsoft.Extensions.Configuration.xml", + "lib/net8.0/Microsoft.Extensions.Configuration.dll", + "lib/net8.0/Microsoft.Extensions.Configuration.xml", + "lib/net9.0/Microsoft.Extensions.Configuration.dll", + "lib/net9.0/Microsoft.Extensions.Configuration.xml", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.dll", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.xml", + "microsoft.extensions.configuration.10.0.3.nupkg.sha512", + "microsoft.extensions.configuration.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Configuration.Abstractions/10.0.3": { + "sha512": "xVDHL0+SIgemfh95fTO9cGLe17TWv/ZP0n7m01z8X6pzt2DmQpucioWR/mYZA1sRlkWnkXzfl0JweLNWmE9WMg==", + "type": "package", + "path": "microsoft.extensions.configuration.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Configuration.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Configuration.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "microsoft.extensions.configuration.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.configuration.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Configuration.Binder/10.0.3": { + "sha512": "759UhpKaR5Jsll9kXpkft4z/7tpeF7Dw2rTY/9f9JchaSQTpRFNIPkZFZvoo7fFpbjUaqtDlO5aiGpmQrp/EUA==", + "type": "package", + "path": "microsoft.extensions.configuration.binder/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/cs/Microsoft.Extensions.Configuration.Binder.SourceGeneration.dll", + "analyzers/dotnet/cs/cs/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/de/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/es/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/fr/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/it/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/ja/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/ko/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/pl/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/pt-BR/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/ru/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/tr/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/zh-Hans/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/zh-Hant/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets", + "lib/net10.0/Microsoft.Extensions.Configuration.Binder.dll", + "lib/net10.0/Microsoft.Extensions.Configuration.Binder.xml", + "lib/net462/Microsoft.Extensions.Configuration.Binder.dll", + "lib/net462/Microsoft.Extensions.Configuration.Binder.xml", + "lib/net8.0/Microsoft.Extensions.Configuration.Binder.dll", + "lib/net8.0/Microsoft.Extensions.Configuration.Binder.xml", + "lib/net9.0/Microsoft.Extensions.Configuration.Binder.dll", + "lib/net9.0/Microsoft.Extensions.Configuration.Binder.xml", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Binder.dll", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Binder.xml", + "microsoft.extensions.configuration.binder.10.0.3.nupkg.sha512", + "microsoft.extensions.configuration.binder.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyInjection/10.0.3": { + "sha512": "2DLOmC0EkB2smVK8lPP1PIKEgL1arE3CMp9XSIQB/Y7ev5nnnyuM/PizKJ6QfLD08QCYoopSC9SFdbYglDomYg==", + "type": "package", + "path": "microsoft.extensions.dependencyinjection/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyInjection.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.targets", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/net462/Microsoft.Extensions.DependencyInjection.dll", + "lib/net462/Microsoft.Extensions.DependencyInjection.xml", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.dll", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.xml", + "microsoft.extensions.dependencyinjection.10.0.3.nupkg.sha512", + "microsoft.extensions.dependencyinjection.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/10.0.3": { + "sha512": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==", + "type": "package", + "path": "microsoft.extensions.dependencyinjection.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyInjection.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "microsoft.extensions.dependencyinjection.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.dependencyinjection.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Diagnostics/10.0.3": { + "sha512": "tc0R6i2T+138taoxFPQXb7Sy/4rtq4ytoJrAt4fNGs6k89mHpEhZnXUNgaFKwwb5Ud5rIUeLC6tfpsuHNwiWqg==", + "type": "package", + "path": "microsoft.extensions.diagnostics/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Diagnostics.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Diagnostics.targets", + "lib/net10.0/Microsoft.Extensions.Diagnostics.dll", + "lib/net10.0/Microsoft.Extensions.Diagnostics.xml", + "lib/net462/Microsoft.Extensions.Diagnostics.dll", + "lib/net462/Microsoft.Extensions.Diagnostics.xml", + "lib/net8.0/Microsoft.Extensions.Diagnostics.dll", + "lib/net8.0/Microsoft.Extensions.Diagnostics.xml", + "lib/net9.0/Microsoft.Extensions.Diagnostics.dll", + "lib/net9.0/Microsoft.Extensions.Diagnostics.xml", + "lib/netstandard2.0/Microsoft.Extensions.Diagnostics.dll", + "lib/netstandard2.0/Microsoft.Extensions.Diagnostics.xml", + "microsoft.extensions.diagnostics.10.0.3.nupkg.sha512", + "microsoft.extensions.diagnostics.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Diagnostics.Abstractions/10.0.3": { + "sha512": "mQiTzAj7PIJ2A9YXR5QhgulS1fTWhmQc3ckd1Mrf3hKW07d03fBDqx8vVaFw+cRTebDOeB6pNqdWdnRxsi1hBA==", + "type": "package", + "path": "microsoft.extensions.diagnostics.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Diagnostics.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Diagnostics.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Diagnostics.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Diagnostics.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Diagnostics.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Diagnostics.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Diagnostics.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Diagnostics.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Diagnostics.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Diagnostics.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Diagnostics.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Diagnostics.Abstractions.xml", + "microsoft.extensions.diagnostics.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.diagnostics.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Http/10.0.3": { + "sha512": "M5gWob3dtzlF14oto1lR1ZuSJrR0gGc+obv7zY9LGmX5y3Ndpve29MrrjqJW/m4CFud4TE/KFUuHjjtwxhCO8g==", + "type": "package", + "path": "microsoft.extensions.http/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Http.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Http.targets", + "lib/net10.0/Microsoft.Extensions.Http.dll", + "lib/net10.0/Microsoft.Extensions.Http.xml", + "lib/net462/Microsoft.Extensions.Http.dll", + "lib/net462/Microsoft.Extensions.Http.xml", + "lib/net8.0/Microsoft.Extensions.Http.dll", + "lib/net8.0/Microsoft.Extensions.Http.xml", + "lib/net9.0/Microsoft.Extensions.Http.dll", + "lib/net9.0/Microsoft.Extensions.Http.xml", + "lib/netstandard2.0/Microsoft.Extensions.Http.dll", + "lib/netstandard2.0/Microsoft.Extensions.Http.xml", + "microsoft.extensions.http.10.0.3.nupkg.sha512", + "microsoft.extensions.http.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Logging/10.0.3": { + "sha512": "8D9Er1cGXNjNDIB+VLBNHn386L5ls2FoiG9a6o12gyn+GG3w6jdfUhzT8dtBnKcevE7/fsVA8MS3FBgFfClFtQ==", + "type": "package", + "path": "microsoft.extensions.logging/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Logging.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Logging.targets", + "lib/net10.0/Microsoft.Extensions.Logging.dll", + "lib/net10.0/Microsoft.Extensions.Logging.xml", + "lib/net462/Microsoft.Extensions.Logging.dll", + "lib/net462/Microsoft.Extensions.Logging.xml", + "lib/net8.0/Microsoft.Extensions.Logging.dll", + "lib/net8.0/Microsoft.Extensions.Logging.xml", + "lib/net9.0/Microsoft.Extensions.Logging.dll", + "lib/net9.0/Microsoft.Extensions.Logging.xml", + "lib/netstandard2.0/Microsoft.Extensions.Logging.dll", + "lib/netstandard2.0/Microsoft.Extensions.Logging.xml", + "lib/netstandard2.1/Microsoft.Extensions.Logging.dll", + "lib/netstandard2.1/Microsoft.Extensions.Logging.xml", + "microsoft.extensions.logging.10.0.3.nupkg.sha512", + "microsoft.extensions.logging.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Logging.Abstractions/10.0.3": { + "sha512": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", + "type": "package", + "path": "microsoft.extensions.logging.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn3.11/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn3.11/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn4.0/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "buildTransitive/net461/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/net462/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.xml", + "microsoft.extensions.logging.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.logging.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Options/10.0.3": { + "sha512": "hU6WzGTPvPoLA2ng1ILvWQb3g0qORdlHNsxI8IcPLumJb3suimYUl+bbDzdo1V4KFsvVhnMWzysHpKbZaoDQPQ==", + "type": "package", + "path": "microsoft.extensions.options/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Options.SourceGeneration.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "buildTransitive/net461/Microsoft.Extensions.Options.targets", + "buildTransitive/net462/Microsoft.Extensions.Options.targets", + "buildTransitive/net8.0/Microsoft.Extensions.Options.targets", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Options.targets", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Options.targets", + "lib/net10.0/Microsoft.Extensions.Options.dll", + "lib/net10.0/Microsoft.Extensions.Options.xml", + "lib/net462/Microsoft.Extensions.Options.dll", + "lib/net462/Microsoft.Extensions.Options.xml", + "lib/net8.0/Microsoft.Extensions.Options.dll", + "lib/net8.0/Microsoft.Extensions.Options.xml", + "lib/net9.0/Microsoft.Extensions.Options.dll", + "lib/net9.0/Microsoft.Extensions.Options.xml", + "lib/netstandard2.0/Microsoft.Extensions.Options.dll", + "lib/netstandard2.0/Microsoft.Extensions.Options.xml", + "lib/netstandard2.1/Microsoft.Extensions.Options.dll", + "lib/netstandard2.1/Microsoft.Extensions.Options.xml", + "microsoft.extensions.options.10.0.3.nupkg.sha512", + "microsoft.extensions.options.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Options.ConfigurationExtensions/10.0.3": { + "sha512": "bn6QoBbbvwmzLIFyxrnL2/e+sqoNUOGbHyfWK9DPONMv1mDCYHm/C7MusYASM31b2lUx6OiDmonb3v+dv5t0nA==", + "type": "package", + "path": "microsoft.extensions.options.configurationextensions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Options.ConfigurationExtensions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Options.ConfigurationExtensions.targets", + "lib/net10.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll", + "lib/net10.0/Microsoft.Extensions.Options.ConfigurationExtensions.xml", + "lib/net462/Microsoft.Extensions.Options.ConfigurationExtensions.dll", + "lib/net462/Microsoft.Extensions.Options.ConfigurationExtensions.xml", + "lib/net8.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll", + "lib/net8.0/Microsoft.Extensions.Options.ConfigurationExtensions.xml", + "lib/net9.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll", + "lib/net9.0/Microsoft.Extensions.Options.ConfigurationExtensions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Options.ConfigurationExtensions.xml", + "microsoft.extensions.options.configurationextensions.10.0.3.nupkg.sha512", + "microsoft.extensions.options.configurationextensions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Primitives/10.0.3": { + "sha512": "GEcpTwo7sUoLGGNTqV1FZEuL+tTD9m81NX/mh099dqGNna07/UGZShKQNZRw4hv6nlliSUwYQgSYc7OR99Jufg==", + "type": "package", + "path": "microsoft.extensions.primitives/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Primitives.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Primitives.targets", + "lib/net10.0/Microsoft.Extensions.Primitives.dll", + "lib/net10.0/Microsoft.Extensions.Primitives.xml", + "lib/net462/Microsoft.Extensions.Primitives.dll", + "lib/net462/Microsoft.Extensions.Primitives.xml", + "lib/net8.0/Microsoft.Extensions.Primitives.dll", + "lib/net8.0/Microsoft.Extensions.Primitives.xml", + "lib/net9.0/Microsoft.Extensions.Primitives.dll", + "lib/net9.0/Microsoft.Extensions.Primitives.xml", + "lib/netstandard2.0/Microsoft.Extensions.Primitives.dll", + "lib/netstandard2.0/Microsoft.Extensions.Primitives.xml", + "microsoft.extensions.primitives.10.0.3.nupkg.sha512", + "microsoft.extensions.primitives.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.OpenApi/1.6.22": { + "sha512": "aBvunmrdu/x+4CaA/UP1Jx4xWGwk4kymhoIRnn2Vp+zi5/KOPQJ9EkSXHRUr01WcGKtYl3Au7XfkPJbU1G2sjQ==", + "type": "package", + "path": "microsoft.openapi/1.6.22", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/netstandard2.0/Microsoft.OpenApi.dll", + "lib/netstandard2.0/Microsoft.OpenApi.pdb", + "lib/netstandard2.0/Microsoft.OpenApi.xml", + "microsoft.openapi.1.6.22.nupkg.sha512", + "microsoft.openapi.nuspec" + ] + }, + "Microsoft.Win32.SystemEvents/9.0.2": { + "sha512": "5BkGZ6mHp2dHydR29sb0fDfAuqkv30AHtTih8wMzvPZysOmBFvHfnkR2w3tsc0pSiIg8ZoKyefJXWy9r3pBh0w==", + "type": "package", + "path": "microsoft.win32.systemevents/9.0.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Win32.SystemEvents.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Win32.SystemEvents.targets", + "lib/net462/Microsoft.Win32.SystemEvents.dll", + "lib/net462/Microsoft.Win32.SystemEvents.xml", + "lib/net8.0/Microsoft.Win32.SystemEvents.dll", + "lib/net8.0/Microsoft.Win32.SystemEvents.xml", + "lib/net9.0/Microsoft.Win32.SystemEvents.dll", + "lib/net9.0/Microsoft.Win32.SystemEvents.xml", + "lib/netstandard2.0/Microsoft.Win32.SystemEvents.dll", + "lib/netstandard2.0/Microsoft.Win32.SystemEvents.xml", + "microsoft.win32.systemevents.9.0.2.nupkg.sha512", + "microsoft.win32.systemevents.nuspec", + "runtimes/win/lib/net8.0/Microsoft.Win32.SystemEvents.dll", + "runtimes/win/lib/net8.0/Microsoft.Win32.SystemEvents.xml", + "runtimes/win/lib/net9.0/Microsoft.Win32.SystemEvents.dll", + "runtimes/win/lib/net9.0/Microsoft.Win32.SystemEvents.xml", + "useSharedDesignerContext.txt" + ] + }, + "NEbml/1.1.0.5": { + "sha512": "svtqDc+hue9kbnqNN2KkK4om/hDrc7K127cNb5FIYfgKgzo+JNDPXNLp8NioCchHhBO3lxWd4Cp/iiZZ3aoUqg==", + "type": "package", + "path": "nebml/1.1.0.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net461/NEbml.Core.dll", + "lib/net461/NEbml.Core.xml", + "lib/netstandard2.0/NEbml.Core.dll", + "lib/netstandard2.0/NEbml.Core.xml", + "nebml.1.1.0.5.nupkg.sha512", + "nebml.nuspec" + ] + }, + "Polly/8.6.5": { + "sha512": "VqtW2ZE/ALvQMAH1cQY3qZ2cF2OXa3oe/HKMdOv6Q02HCoEW0rsFNfcBONXlHBe1TnjWW1vdRxBEkPeq0/2FHA==", + "type": "package", + "path": "polly/8.6.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "lib/net462/Polly.dll", + "lib/net462/Polly.pdb", + "lib/net462/Polly.xml", + "lib/net472/Polly.dll", + "lib/net472/Polly.pdb", + "lib/net472/Polly.xml", + "lib/net6.0/Polly.dll", + "lib/net6.0/Polly.pdb", + "lib/net6.0/Polly.xml", + "lib/netstandard2.0/Polly.dll", + "lib/netstandard2.0/Polly.pdb", + "lib/netstandard2.0/Polly.xml", + "package-icon.png", + "package-readme.md", + "polly.8.6.5.nupkg.sha512", + "polly.nuspec" + ] + }, + "Polly.Core/8.6.5": { + "sha512": "t+sUVrIwvo7UmsgHGgOG9F0GDZSRIm47u2ylH17Gvcv1q5hNEwgD5GoBlFyc0kh/pebmPyrAgvGsR/65ZBaXlg==", + "type": "package", + "path": "polly.core/8.6.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "lib/net462/Polly.Core.dll", + "lib/net462/Polly.Core.pdb", + "lib/net462/Polly.Core.xml", + "lib/net472/Polly.Core.dll", + "lib/net472/Polly.Core.pdb", + "lib/net472/Polly.Core.xml", + "lib/net6.0/Polly.Core.dll", + "lib/net6.0/Polly.Core.pdb", + "lib/net6.0/Polly.Core.xml", + "lib/net8.0/Polly.Core.dll", + "lib/net8.0/Polly.Core.pdb", + "lib/net8.0/Polly.Core.xml", + "lib/netstandard2.0/Polly.Core.dll", + "lib/netstandard2.0/Polly.Core.pdb", + "lib/netstandard2.0/Polly.Core.xml", + "package-icon.png", + "package-readme.md", + "polly.core.8.6.5.nupkg.sha512", + "polly.core.nuspec" + ] + }, + "SerilogAnalyzer/0.15.0": { + "sha512": "sVpwfls4MfNnwIXLSGCgaUnV+c9kgJ8ia6GsyRcpd4Vs3gLogSDtSYBYrre2K2u/PNMo8GgG09RehwVnze70Tw==", + "type": "package", + "path": "seriloganalyzer/0.15.0", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/SerilogAnalyzer.dll", + "seriloganalyzer.0.15.0.nupkg.sha512", + "seriloganalyzer.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "SmartAnalyzers.MultithreadingAnalyzer/1.1.31": { + "sha512": "2f2k7bbhDd132ArglCKpzKoWBcp3uzbIFcb4aosnlqIKlfYKDE2HevBVRNVa+LkWFnjXFFWs47Bo96fu8iS//Q==", + "type": "package", + "path": "smartanalyzers.multithreadinganalyzer/1.1.31", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/SmartAnalyzers.MultithreadingAnalyzer.dll", + "smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512", + "smartanalyzers.multithreadinganalyzer.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "StyleCop.Analyzers/1.2.0-beta.556": { + "sha512": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", + "type": "package", + "path": "stylecop.analyzers/1.2.0-beta.556", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "THIRD-PARTY-NOTICES.txt", + "stylecop.analyzers.1.2.0-beta.556.nupkg.sha512", + "stylecop.analyzers.nuspec" + ] + }, + "StyleCop.Analyzers.Unstable/1.2.0.556": { + "sha512": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==", + "type": "package", + "path": "stylecop.analyzers.unstable/1.2.0.556", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "THIRD-PARTY-NOTICES.txt", + "analyzers/dotnet/cs/StyleCop.Analyzers.CodeFixes.dll", + "analyzers/dotnet/cs/StyleCop.Analyzers.dll", + "analyzers/dotnet/cs/de-DE/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/en-GB/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/es-MX/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/fr-FR/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/pl-PL/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/pt-BR/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/ru-RU/StyleCop.Analyzers.resources.dll", + "rulesets/StyleCopAnalyzersDefault.ruleset", + "stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512", + "stylecop.analyzers.unstable.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Swashbuckle.AspNetCore/7.3.2": { + "sha512": "WEkW7Cl8bexT75eURuqrqM/TQx+ftWKOWlCHzmw3Fl3oAmxiBO/lDVOuuqZsaBCHQIBcZxA6BbyiG5dv2mD2tw==", + "type": "package", + "path": "swashbuckle.aspnetcore/7.3.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/Swashbuckle.AspNetCore.props", + "buildMultiTargeting/Swashbuckle.AspNetCore.props", + "docs/package-readme.md", + "swashbuckle.aspnetcore.7.3.2.nupkg.sha512", + "swashbuckle.aspnetcore.nuspec" + ] + }, + "Swashbuckle.AspNetCore.ReDoc/6.9.0": { + "sha512": "E/KCx9LRDgWD7vr+BHyLdC2dZMuLhwOMp4xjuJOJChoeWQYVpnOYRdQngeZxGFYGxiahEJ2B1fCvL3XTvzArOQ==", + "type": "package", + "path": "swashbuckle.aspnetcore.redoc/6.9.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net5.0/Swashbuckle.AspNetCore.ReDoc.dll", + "lib/net5.0/Swashbuckle.AspNetCore.ReDoc.pdb", + "lib/net5.0/Swashbuckle.AspNetCore.ReDoc.xml", + "lib/net6.0/Swashbuckle.AspNetCore.ReDoc.dll", + "lib/net6.0/Swashbuckle.AspNetCore.ReDoc.pdb", + "lib/net6.0/Swashbuckle.AspNetCore.ReDoc.xml", + "lib/net7.0/Swashbuckle.AspNetCore.ReDoc.dll", + "lib/net7.0/Swashbuckle.AspNetCore.ReDoc.pdb", + "lib/net7.0/Swashbuckle.AspNetCore.ReDoc.xml", + "lib/net8.0/Swashbuckle.AspNetCore.ReDoc.dll", + "lib/net8.0/Swashbuckle.AspNetCore.ReDoc.pdb", + "lib/net8.0/Swashbuckle.AspNetCore.ReDoc.xml", + "lib/netcoreapp3.0/Swashbuckle.AspNetCore.ReDoc.dll", + "lib/netcoreapp3.0/Swashbuckle.AspNetCore.ReDoc.pdb", + "lib/netcoreapp3.0/Swashbuckle.AspNetCore.ReDoc.xml", + "lib/netstandard2.0/Swashbuckle.AspNetCore.ReDoc.dll", + "lib/netstandard2.0/Swashbuckle.AspNetCore.ReDoc.pdb", + "lib/netstandard2.0/Swashbuckle.AspNetCore.ReDoc.xml", + "package-readme.md", + "swashbuckle.aspnetcore.redoc.6.9.0.nupkg.sha512", + "swashbuckle.aspnetcore.redoc.nuspec" + ] + }, + "Swashbuckle.AspNetCore.Swagger/7.3.2": { + "sha512": "39Z2N3cLHDcDinu2IW5IaQO9wGPKTbc+vKbjIT/6gN3jFYypWY5U3O8i7Y9rmYDgxkm6niICnkcFZ3hEvikIHA==", + "type": "package", + "path": "swashbuckle.aspnetcore.swagger/7.3.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net6.0/Swashbuckle.AspNetCore.Swagger.dll", + "lib/net6.0/Swashbuckle.AspNetCore.Swagger.pdb", + "lib/net6.0/Swashbuckle.AspNetCore.Swagger.xml", + "lib/net8.0/Swashbuckle.AspNetCore.Swagger.dll", + "lib/net8.0/Swashbuckle.AspNetCore.Swagger.pdb", + "lib/net8.0/Swashbuckle.AspNetCore.Swagger.xml", + "lib/net9.0/Swashbuckle.AspNetCore.Swagger.dll", + "lib/net9.0/Swashbuckle.AspNetCore.Swagger.pdb", + "lib/net9.0/Swashbuckle.AspNetCore.Swagger.xml", + "lib/netstandard2.0/Swashbuckle.AspNetCore.Swagger.dll", + "lib/netstandard2.0/Swashbuckle.AspNetCore.Swagger.pdb", + "lib/netstandard2.0/Swashbuckle.AspNetCore.Swagger.xml", + "package-readme.md", + "swashbuckle.aspnetcore.swagger.7.3.2.nupkg.sha512", + "swashbuckle.aspnetcore.swagger.nuspec" + ] + }, + "Swashbuckle.AspNetCore.SwaggerGen/7.3.2": { + "sha512": "x5famjpcgazpT/8eEIlpSYSTZDq6ojMARhuJgj6O67Uk+Yb1DdDM5yikdL7GK1pMk5rYBETUCRWYP6rp+vHnlA==", + "type": "package", + "path": "swashbuckle.aspnetcore.swaggergen/7.3.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net6.0/Swashbuckle.AspNetCore.SwaggerGen.dll", + "lib/net6.0/Swashbuckle.AspNetCore.SwaggerGen.pdb", + "lib/net6.0/Swashbuckle.AspNetCore.SwaggerGen.xml", + "lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.dll", + "lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.pdb", + "lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.xml", + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll", + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.pdb", + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.xml", + "lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerGen.dll", + "lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerGen.pdb", + "lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerGen.xml", + "package-readme.md", + "swashbuckle.aspnetcore.swaggergen.7.3.2.nupkg.sha512", + "swashbuckle.aspnetcore.swaggergen.nuspec" + ] + }, + "Swashbuckle.AspNetCore.SwaggerUI/7.3.2": { + "sha512": "SNQuFjYin9KhkoCSVOD5/Y4VEyAjKBDeZkIqqY6x+oI21HMKzzVkeJA9V7tWFXf5df4m/+pTIlbKO7g5W9UroQ==", + "type": "package", + "path": "swashbuckle.aspnetcore.swaggerui/7.3.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net6.0/Swashbuckle.AspNetCore.SwaggerUI.dll", + "lib/net6.0/Swashbuckle.AspNetCore.SwaggerUI.pdb", + "lib/net6.0/Swashbuckle.AspNetCore.SwaggerUI.xml", + "lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.dll", + "lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.pdb", + "lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.xml", + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll", + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.pdb", + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.xml", + "lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerUI.dll", + "lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerUI.pdb", + "lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerUI.xml", + "package-readme.md", + "swashbuckle.aspnetcore.swaggerui.7.3.2.nupkg.sha512", + "swashbuckle.aspnetcore.swaggerui.nuspec" + ] + }, + "System.Drawing.Common/9.0.2": { + "sha512": "JU947wzf8JbBS16Y5EIZzAlyQU+k68D7LRx6y03s2wlhlvLqkt/8uPBrjv2hJnnaJKbdb0GhQ3JZsfYXhrRjyg==", + "type": "package", + "path": "system.drawing.common/9.0.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Drawing.Common.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/System.Drawing.Common.targets", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net462/System.Drawing.Common.dll", + "lib/net462/System.Drawing.Common.pdb", + "lib/net462/System.Drawing.Common.xml", + "lib/net8.0/System.Drawing.Common.dll", + "lib/net8.0/System.Drawing.Common.pdb", + "lib/net8.0/System.Drawing.Common.xml", + "lib/net8.0/System.Private.Windows.Core.dll", + "lib/net8.0/System.Private.Windows.Core.xml", + "lib/net9.0/System.Drawing.Common.dll", + "lib/net9.0/System.Drawing.Common.pdb", + "lib/net9.0/System.Drawing.Common.xml", + "lib/net9.0/System.Private.Windows.Core.dll", + "lib/net9.0/System.Private.Windows.Core.xml", + "lib/netstandard2.0/System.Drawing.Common.dll", + "lib/netstandard2.0/System.Drawing.Common.pdb", + "lib/netstandard2.0/System.Drawing.Common.xml", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "system.drawing.common.9.0.2.nupkg.sha512", + "system.drawing.common.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "UTF.Unknown/2.6.0": { + "sha512": "YmmUcR9XKh3twmSjq6l0HD7qm7bj3X+gXCsdkWwpiuiB4wzGDJuCOneOQ3NvwQQEMqNeyVZOPO/EwFD4fuUoOQ==", + "type": "package", + "path": "utf.unknown/2.6.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net6.0/UtfUnknown.dll", + "lib/net6.0/UtfUnknown.xml", + "lib/net8.0/UtfUnknown.dll", + "lib/net8.0/UtfUnknown.xml", + "lib/netstandard2.0/UtfUnknown.dll", + "lib/netstandard2.0/UtfUnknown.xml", + "utf.unknown.2.6.0.nupkg.sha512", + "utf.unknown.nuspec" + ] + }, + "zlib.net-mutliplatform/1.0.8": { + "sha512": "6sYoMfSmDdfG8HgxXi0ydwCMUv5Ia1gMRh2Q0JLlTUxE9ZeWJ9dir/1Hq5MW6JcOZARRsZIOGecVx6WuAoloEA==", + "type": "package", + "path": "zlib.net-mutliplatform/1.0.8", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/netstandard1.6/zlib.net.dll", + "lib/netstandard2.0/zlib.net.dll", + "lib/netstandard2.1/zlib.net.dll", + "zlib.net-mutliplatform.1.0.8.nupkg.sha512", + "zlib.net-mutliplatform.nuspec" + ] + }, + "Jellyfin.CodeAnalysis/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "msbuildProject": "../src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "Jellyfin.Common/10.12.0": { + "type": "project", + "path": "../MediaBrowser.Common/MediaBrowser.Common.csproj", + "msbuildProject": "../MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "Jellyfin.Controller/10.12.0": { + "type": "project", + "path": "../MediaBrowser.Controller/MediaBrowser.Controller.csproj", + "msbuildProject": "../MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "Jellyfin.Data/10.12.0": { + "type": "project", + "path": "../Jellyfin.Data/Jellyfin.Data.csproj", + "msbuildProject": "../Jellyfin.Data/Jellyfin.Data.csproj" + }, + "Jellyfin.Database.Implementations/10.11.0": { + "type": "project", + "path": "../src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "msbuildProject": "../src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + }, + "Jellyfin.Extensions/10.12.0": { + "type": "project", + "path": "../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "msbuildProject": "../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" + }, + "Jellyfin.MediaEncoding.Hls/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj", + "msbuildProject": "../src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj" + }, + "Jellyfin.MediaEncoding.Keyframes/10.11.0": { + "type": "project", + "path": "../src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj", + "msbuildProject": "../src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj" + }, + "Jellyfin.Model/10.12.0": { + "type": "project", + "path": "../MediaBrowser.Model/MediaBrowser.Model.csproj", + "msbuildProject": "../MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "Jellyfin.Naming/10.12.0": { + "type": "project", + "path": "../Emby.Naming/Emby.Naming.csproj", + "msbuildProject": "../Emby.Naming/Emby.Naming.csproj" + }, + "Jellyfin.Networking/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.Networking/Jellyfin.Networking.csproj", + "msbuildProject": "../src/Jellyfin.Networking/Jellyfin.Networking.csproj" + }, + "MediaBrowser.MediaEncoding/1.0.0": { + "type": "project", + "path": "../MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj", + "msbuildProject": "../MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj" + } + }, + "projectFileDependencyGroups": { + "net10.0": [ + "IDisposableAnalyzers >= 4.0.8", + "Jellyfin.CodeAnalysis >= 1.0.0", + "Jellyfin.Controller >= 10.12.0", + "Jellyfin.MediaEncoding.Hls >= 1.0.0", + "Jellyfin.Networking >= 1.0.0", + "MediaBrowser.MediaEncoding >= 1.0.0", + "Microsoft.AspNetCore.Authorization >= 10.0.3", + "Microsoft.CodeAnalysis.BannedApiAnalyzers >= 4.14.0", + "Microsoft.Extensions.Http >= 10.0.3", + "SerilogAnalyzer >= 0.15.0", + "SmartAnalyzers.MultithreadingAnalyzer >= 1.1.31", + "StyleCop.Analyzers >= 1.2.0-beta.556", + "Swashbuckle.AspNetCore >= 7.3.2", + "Swashbuckle.AspNetCore.ReDoc >= 6.9.0" + ] + }, + "packageFolders": { + "/home/wjones/.nuget/packages/": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj", + "projectName": "Jellyfin.Api", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.AspNetCore.Authorization": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Http": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "Swashbuckle.AspNetCore": { + "target": "Package", + "version": "[7.3.2, )", + "versionCentrallyManaged": true + }, + "Swashbuckle.AspNetCore.ReDoc": { + "target": "Package", + "version": "[6.9.0, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + } +} \ No newline at end of file diff --git a/Jellyfin.Api/obj/project.nuget.cache b/Jellyfin.Api/obj/project.nuget.cache new file mode 100644 index 00000000..6a0e3591 --- /dev/null +++ b/Jellyfin.Api/obj/project.nuget.cache @@ -0,0 +1,58 @@ +{ + "version": 2, + "dgSpecHash": "j6PAliGuV+s=", + "success": true, + "projectFilePath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj", + "expectedPackageFiles": [ + "/home/wjones/.nuget/packages/asynckeyedlock/8.0.2/asynckeyedlock.8.0.2.nupkg.sha512", + "/home/wjones/.nuget/packages/bdinfo/0.8.0/bdinfo.0.8.0.nupkg.sha512", + "/home/wjones/.nuget/packages/bitfaster.caching/2.5.4/bitfaster.caching.2.5.4.nupkg.sha512", + "/home/wjones/.nuget/packages/diacritics/4.1.4/diacritics.4.1.4.nupkg.sha512", + "/home/wjones/.nuget/packages/icu4n/60.1.0-alpha.356/icu4n.60.1.0-alpha.356.nupkg.sha512", + "/home/wjones/.nuget/packages/icu4n.transliterator/60.1.0-alpha.356/icu4n.transliterator.60.1.0-alpha.356.nupkg.sha512", + "/home/wjones/.nuget/packages/idisposableanalyzers/4.0.8/idisposableanalyzers.4.0.8.nupkg.sha512", + "/home/wjones/.nuget/packages/j2n/2.0.0/j2n.2.0.0.nupkg.sha512", + "/home/wjones/.nuget/packages/libse/4.0.12/libse.4.0.12.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.aspnetcore.authorization/10.0.3/microsoft.aspnetcore.authorization.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.aspnetcore.metadata/10.0.3/microsoft.aspnetcore.metadata.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0/microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore/10.0.3/microsoft.entityframeworkcore.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.abstractions/10.0.3/microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.analyzers/10.0.3/microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.relational/10.0.3/microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.apidescription.server/6.0.5/microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.caching.abstractions/10.0.3/microsoft.extensions.caching.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.caching.memory/10.0.3/microsoft.extensions.caching.memory.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.configuration/10.0.3/microsoft.extensions.configuration.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.configuration.abstractions/10.0.3/microsoft.extensions.configuration.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.configuration.binder/10.0.3/microsoft.extensions.configuration.binder.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection/10.0.3/microsoft.extensions.dependencyinjection.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/10.0.3/microsoft.extensions.dependencyinjection.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.diagnostics/10.0.3/microsoft.extensions.diagnostics.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.diagnostics.abstractions/10.0.3/microsoft.extensions.diagnostics.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.http/10.0.3/microsoft.extensions.http.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.logging/10.0.3/microsoft.extensions.logging.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.logging.abstractions/10.0.3/microsoft.extensions.logging.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.options/10.0.3/microsoft.extensions.options.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.options.configurationextensions/10.0.3/microsoft.extensions.options.configurationextensions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.primitives/10.0.3/microsoft.extensions.primitives.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.openapi/1.6.22/microsoft.openapi.1.6.22.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.win32.systemevents/9.0.2/microsoft.win32.systemevents.9.0.2.nupkg.sha512", + "/home/wjones/.nuget/packages/nebml/1.1.0.5/nebml.1.1.0.5.nupkg.sha512", + "/home/wjones/.nuget/packages/polly/8.6.5/polly.8.6.5.nupkg.sha512", + "/home/wjones/.nuget/packages/polly.core/8.6.5/polly.core.8.6.5.nupkg.sha512", + "/home/wjones/.nuget/packages/seriloganalyzer/0.15.0/seriloganalyzer.0.15.0.nupkg.sha512", + "/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31/smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512", + "/home/wjones/.nuget/packages/stylecop.analyzers/1.2.0-beta.556/stylecop.analyzers.1.2.0-beta.556.nupkg.sha512", + "/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556/stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512", + "/home/wjones/.nuget/packages/swashbuckle.aspnetcore/7.3.2/swashbuckle.aspnetcore.7.3.2.nupkg.sha512", + "/home/wjones/.nuget/packages/swashbuckle.aspnetcore.redoc/6.9.0/swashbuckle.aspnetcore.redoc.6.9.0.nupkg.sha512", + "/home/wjones/.nuget/packages/swashbuckle.aspnetcore.swagger/7.3.2/swashbuckle.aspnetcore.swagger.7.3.2.nupkg.sha512", + "/home/wjones/.nuget/packages/swashbuckle.aspnetcore.swaggergen/7.3.2/swashbuckle.aspnetcore.swaggergen.7.3.2.nupkg.sha512", + "/home/wjones/.nuget/packages/swashbuckle.aspnetcore.swaggerui/7.3.2/swashbuckle.aspnetcore.swaggerui.7.3.2.nupkg.sha512", + "/home/wjones/.nuget/packages/system.drawing.common/9.0.2/system.drawing.common.9.0.2.nupkg.sha512", + "/home/wjones/.nuget/packages/utf.unknown/2.6.0/utf.unknown.2.6.0.nupkg.sha512", + "/home/wjones/.nuget/packages/zlib.net-mutliplatform/1.0.8/zlib.net-mutliplatform.1.0.8.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Jellyfin.Api/obj/project.packagespec.json b/Jellyfin.Api/obj/project.packagespec.json new file mode 100644 index 00000000..e831406c --- /dev/null +++ b/Jellyfin.Api/obj/project.packagespec.json @@ -0,0 +1 @@ +"restore":{"projectUniqueName":"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj","projectName":"Jellyfin.Api","projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj","outputPath":"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/obj/","projectStyle":"PackageReference","centralPackageVersionsManagementEnabled":true,"originalTargetFrameworks":["net10.0"],"sources":{"/usr/lib/dotnet/library-packs":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net10.0":{"targetAlias":"net10.0","projectReferences":{"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj"}}}},"warningProperties":{"allWarningsAsErrors":true,"warnAsError":["NU1605"],"warnNotAsError":["NU1902","NU1903"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"all"},"SdkAnalysisLevel":"10.0.100"}"frameworks":{"net10.0":{"targetAlias":"net10.0","dependencies":{"IDisposableAnalyzers":{"include":"Runtime, Build, Native, ContentFiles, Analyzers","suppressParent":"All","target":"Package","version":"[4.0.8, )","versionCentrallyManaged":true},"Microsoft.AspNetCore.Authorization":{"target":"Package","version":"[10.0.3, )","versionCentrallyManaged":true},"Microsoft.CodeAnalysis.BannedApiAnalyzers":{"include":"Runtime, Build, Native, ContentFiles, Analyzers","suppressParent":"All","target":"Package","version":"[4.14.0, )","versionCentrallyManaged":true},"Microsoft.Extensions.Http":{"target":"Package","version":"[10.0.3, )","versionCentrallyManaged":true},"SerilogAnalyzer":{"suppressParent":"All","target":"Package","version":"[0.15.0, )","versionCentrallyManaged":true},"SmartAnalyzers.MultithreadingAnalyzer":{"suppressParent":"All","target":"Package","version":"[1.1.31, )","versionCentrallyManaged":true},"StyleCop.Analyzers":{"suppressParent":"All","target":"Package","version":"[1.2.0-beta.556, )","versionCentrallyManaged":true},"Swashbuckle.AspNetCore":{"target":"Package","version":"[7.3.2, )","versionCentrallyManaged":true},"Swashbuckle.AspNetCore.ReDoc":{"target":"Package","version":"[6.9.0, )","versionCentrallyManaged":true}},"centralPackageVersions":{"AsyncKeyedLock":"8.0.2","AutoFixture":"4.18.1","AutoFixture.AutoMoq":"4.18.1","AutoFixture.Xunit2":"4.18.1","BDInfo":"0.8.0","BitFaster.Caching":"2.5.4","BlurHashSharp":"1.4.0-pre.1","BlurHashSharp.SkiaSharp":"1.4.0-pre.1","CommandLineParser":"2.9.1","coverlet.collector":"8.0.0","Diacritics":"4.1.4","DiscUtils.Udf":"0.16.13","DotNet.Glob":"3.1.3","FsCheck.Xunit":"3.3.2","HarfBuzzSharp.NativeAssets.Linux":"8.3.1.1","ICU4N.Transliterator":"60.1.0-alpha.356","IDisposableAnalyzers":"4.0.8","Ignore":"0.2.1","Jellyfin.XmlTv":"10.8.0","libse":"4.0.12","LrcParser":"2025.623.0","MetaBrainz.MusicBrainz":"8.0.1","Microsoft.AspNetCore.Authorization":"10.0.3","Microsoft.AspNetCore.Mvc.Testing":"10.0.3","Microsoft.CodeAnalysis.Analyzers":"3.11.0","Microsoft.CodeAnalysis.BannedApiAnalyzers":"4.14.0","Microsoft.CodeAnalysis.Common":"5.0.0","Microsoft.CodeAnalysis.CSharp":"5.0.0","Microsoft.Data.Sqlite":"10.0.3","Microsoft.EntityFrameworkCore.Design":"10.0.3","Microsoft.EntityFrameworkCore.Relational":"10.0.3","Microsoft.EntityFrameworkCore.Sqlite":"10.0.3","Microsoft.EntityFrameworkCore.Tools":"10.0.3","Microsoft.Extensions.Caching.Abstractions":"10.0.3","Microsoft.Extensions.Caching.Memory":"10.0.3","Microsoft.Extensions.Configuration.Abstractions":"10.0.3","Microsoft.Extensions.Configuration.Binder":"10.0.3","Microsoft.Extensions.DependencyInjection":"10.0.3","Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore":"10.0.3","Microsoft.Extensions.Hosting.Abstractions":"10.0.3","Microsoft.Extensions.Http":"10.0.3","Microsoft.Extensions.Logging":"10.0.3","Microsoft.Extensions.Options":"10.0.3","Microsoft.NET.Test.Sdk":"18.0.1","MimeTypes":"2.5.2","Moq":"4.18.4","Morestachio":"5.0.1.631","NEbml":"1.1.0.5","Newtonsoft.Json":"13.0.4","PlaylistsNET":"1.4.1","Polly":"8.6.5","prometheus-net":"8.2.1","prometheus-net.AspNetCore":"8.2.1","prometheus-net.DotNetRuntime":"4.4.1","Serilog.AspNetCore":"10.0.0","Serilog.Enrichers.Thread":"4.0.0","Serilog.Expressions":"5.0.0","Serilog.Settings.Configuration":"10.0.0","Serilog.Sinks.Async":"2.1.0","Serilog.Sinks.Console":"6.1.1","Serilog.Sinks.File":"7.0.0","Serilog.Sinks.Graylog":"3.1.1","SerilogAnalyzer":"0.15.0","SharpFuzz":"2.2.0","SkiaSharp":"[3.116.1]","SkiaSharp.HarfBuzz":"[3.116.1]","SkiaSharp.NativeAssets.Linux":"[3.116.1]","SmartAnalyzers.MultithreadingAnalyzer":"1.1.31","StyleCop.Analyzers":"1.2.0-beta.556","Svg.Skia":"3.4.1","Swashbuckle.AspNetCore":"7.3.2","Swashbuckle.AspNetCore.ReDoc":"6.9.0","System.Text.Json":"10.0.3","TagLibSharp":"2.3.0","TMDbLib":"2.3.0","UTF.Unknown":"2.6.0","xunit":"2.9.3","Xunit.Priority":"1.1.6","xunit.runner.visualstudio":"2.8.2","Xunit.SkippableFact":"1.5.61","z440.atl.core":"7.11.0"},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json","packagesToPrune":{"Microsoft.CSharp":"(,4.7.32767]","Microsoft.VisualBasic":"(,10.4.32767]","Microsoft.Win32.Primitives":"(,4.3.32767]","Microsoft.Win32.Registry":"(,5.0.32767]","runtime.any.System.Collections":"(,4.3.32767]","runtime.any.System.Diagnostics.Tools":"(,4.3.32767]","runtime.any.System.Diagnostics.Tracing":"(,4.3.32767]","runtime.any.System.Globalization":"(,4.3.32767]","runtime.any.System.Globalization.Calendars":"(,4.3.32767]","runtime.any.System.IO":"(,4.3.32767]","runtime.any.System.Reflection":"(,4.3.32767]","runtime.any.System.Reflection.Extensions":"(,4.3.32767]","runtime.any.System.Reflection.Primitives":"(,4.3.32767]","runtime.any.System.Resources.ResourceManager":"(,4.3.32767]","runtime.any.System.Runtime":"(,4.3.32767]","runtime.any.System.Runtime.Handles":"(,4.3.32767]","runtime.any.System.Runtime.InteropServices":"(,4.3.32767]","runtime.any.System.Text.Encoding":"(,4.3.32767]","runtime.any.System.Text.Encoding.Extensions":"(,4.3.32767]","runtime.any.System.Threading.Tasks":"(,4.3.32767]","runtime.any.System.Threading.Timer":"(,4.3.32767]","runtime.aot.System.Collections":"(,4.3.32767]","runtime.aot.System.Diagnostics.Tools":"(,4.3.32767]","runtime.aot.System.Diagnostics.Tracing":"(,4.3.32767]","runtime.aot.System.Globalization":"(,4.3.32767]","runtime.aot.System.Globalization.Calendars":"(,4.3.32767]","runtime.aot.System.IO":"(,4.3.32767]","runtime.aot.System.Reflection":"(,4.3.32767]","runtime.aot.System.Reflection.Extensions":"(,4.3.32767]","runtime.aot.System.Reflection.Primitives":"(,4.3.32767]","runtime.aot.System.Resources.ResourceManager":"(,4.3.32767]","runtime.aot.System.Runtime":"(,4.3.32767]","runtime.aot.System.Runtime.Handles":"(,4.3.32767]","runtime.aot.System.Runtime.InteropServices":"(,4.3.32767]","runtime.aot.System.Text.Encoding":"(,4.3.32767]","runtime.aot.System.Text.Encoding.Extensions":"(,4.3.32767]","runtime.aot.System.Threading.Tasks":"(,4.3.32767]","runtime.aot.System.Threading.Timer":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.unix.Microsoft.Win32.Primitives":"(,4.3.32767]","runtime.unix.System.Console":"(,4.3.32767]","runtime.unix.System.Diagnostics.Debug":"(,4.3.32767]","runtime.unix.System.IO.FileSystem":"(,4.3.32767]","runtime.unix.System.Net.Primitives":"(,4.3.32767]","runtime.unix.System.Net.Sockets":"(,4.3.32767]","runtime.unix.System.Private.Uri":"(,4.3.32767]","runtime.unix.System.Runtime.Extensions":"(,4.3.32767]","runtime.win.Microsoft.Win32.Primitives":"(,4.3.32767]","runtime.win.System.Console":"(,4.3.32767]","runtime.win.System.Diagnostics.Debug":"(,4.3.32767]","runtime.win.System.IO.FileSystem":"(,4.3.32767]","runtime.win.System.Net.Primitives":"(,4.3.32767]","runtime.win.System.Net.Sockets":"(,4.3.32767]","runtime.win.System.Runtime.Extensions":"(,4.3.32767]","runtime.win10-arm-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win10-arm64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win10-x64-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win10-x86-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win7-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win7-x86.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win7.System.Private.Uri":"(,4.3.32767]","runtime.win8-arm.runtime.native.System.IO.Compression":"(,4.3.32767]","System.AppContext":"(,4.3.32767]","System.Buffers":"(,5.0.32767]","System.Collections":"(,4.3.32767]","System.Collections.Concurrent":"(,4.3.32767]","System.Collections.Immutable":"(,10.0.32767]","System.Collections.NonGeneric":"(,4.3.32767]","System.Collections.Specialized":"(,4.3.32767]","System.ComponentModel":"(,4.3.32767]","System.ComponentModel.Annotations":"(,4.3.32767]","System.ComponentModel.EventBasedAsync":"(,4.3.32767]","System.ComponentModel.Primitives":"(,4.3.32767]","System.ComponentModel.TypeConverter":"(,4.3.32767]","System.Console":"(,4.3.32767]","System.Data.Common":"(,4.3.32767]","System.Data.DataSetExtensions":"(,4.4.32767]","System.Diagnostics.Contracts":"(,4.3.32767]","System.Diagnostics.Debug":"(,4.3.32767]","System.Diagnostics.DiagnosticSource":"(,10.0.32767]","System.Diagnostics.FileVersionInfo":"(,4.3.32767]","System.Diagnostics.Process":"(,4.3.32767]","System.Diagnostics.StackTrace":"(,4.3.32767]","System.Diagnostics.TextWriterTraceListener":"(,4.3.32767]","System.Diagnostics.Tools":"(,4.3.32767]","System.Diagnostics.TraceSource":"(,4.3.32767]","System.Diagnostics.Tracing":"(,4.3.32767]","System.Drawing.Primitives":"(,4.3.32767]","System.Dynamic.Runtime":"(,4.3.32767]","System.Formats.Asn1":"(,10.0.32767]","System.Formats.Tar":"(,10.0.32767]","System.Globalization":"(,4.3.32767]","System.Globalization.Calendars":"(,4.3.32767]","System.Globalization.Extensions":"(,4.3.32767]","System.IO":"(,4.3.32767]","System.IO.Compression":"(,4.3.32767]","System.IO.Compression.ZipFile":"(,4.3.32767]","System.IO.FileSystem":"(,4.3.32767]","System.IO.FileSystem.AccessControl":"(,4.4.32767]","System.IO.FileSystem.DriveInfo":"(,4.3.32767]","System.IO.FileSystem.Primitives":"(,4.3.32767]","System.IO.FileSystem.Watcher":"(,4.3.32767]","System.IO.IsolatedStorage":"(,4.3.32767]","System.IO.MemoryMappedFiles":"(,4.3.32767]","System.IO.Pipelines":"(,10.0.32767]","System.IO.Pipes":"(,4.3.32767]","System.IO.Pipes.AccessControl":"(,5.0.32767]","System.IO.UnmanagedMemoryStream":"(,4.3.32767]","System.Linq":"(,4.3.32767]","System.Linq.AsyncEnumerable":"(,10.0.32767]","System.Linq.Expressions":"(,4.3.32767]","System.Linq.Parallel":"(,4.3.32767]","System.Linq.Queryable":"(,4.3.32767]","System.Memory":"(,5.0.32767]","System.Net.Http":"(,4.3.32767]","System.Net.Http.Json":"(,10.0.32767]","System.Net.NameResolution":"(,4.3.32767]","System.Net.NetworkInformation":"(,4.3.32767]","System.Net.Ping":"(,4.3.32767]","System.Net.Primitives":"(,4.3.32767]","System.Net.Requests":"(,4.3.32767]","System.Net.Security":"(,4.3.32767]","System.Net.ServerSentEvents":"(,10.0.32767]","System.Net.Sockets":"(,4.3.32767]","System.Net.WebHeaderCollection":"(,4.3.32767]","System.Net.WebSockets":"(,4.3.32767]","System.Net.WebSockets.Client":"(,4.3.32767]","System.Numerics.Vectors":"(,5.0.32767]","System.ObjectModel":"(,4.3.32767]","System.Private.DataContractSerialization":"(,4.3.32767]","System.Private.Uri":"(,4.3.32767]","System.Reflection":"(,4.3.32767]","System.Reflection.DispatchProxy":"(,6.0.32767]","System.Reflection.Emit":"(,4.7.32767]","System.Reflection.Emit.ILGeneration":"(,4.7.32767]","System.Reflection.Emit.Lightweight":"(,4.7.32767]","System.Reflection.Extensions":"(,4.3.32767]","System.Reflection.Metadata":"(,10.0.32767]","System.Reflection.Primitives":"(,4.3.32767]","System.Reflection.TypeExtensions":"(,4.3.32767]","System.Resources.Reader":"(,4.3.32767]","System.Resources.ResourceManager":"(,4.3.32767]","System.Resources.Writer":"(,4.3.32767]","System.Runtime":"(,4.3.32767]","System.Runtime.CompilerServices.Unsafe":"(,7.0.32767]","System.Runtime.CompilerServices.VisualC":"(,4.3.32767]","System.Runtime.Extensions":"(,4.3.32767]","System.Runtime.Handles":"(,4.3.32767]","System.Runtime.InteropServices":"(,4.3.32767]","System.Runtime.InteropServices.RuntimeInformation":"(,4.3.32767]","System.Runtime.Loader":"(,4.3.32767]","System.Runtime.Numerics":"(,4.3.32767]","System.Runtime.Serialization.Formatters":"(,4.3.32767]","System.Runtime.Serialization.Json":"(,4.3.32767]","System.Runtime.Serialization.Primitives":"(,4.3.32767]","System.Runtime.Serialization.Xml":"(,4.3.32767]","System.Security.AccessControl":"(,6.0.32767]","System.Security.Claims":"(,4.3.32767]","System.Security.Cryptography.Algorithms":"(,4.3.32767]","System.Security.Cryptography.Cng":"(,5.0.32767]","System.Security.Cryptography.Csp":"(,4.3.32767]","System.Security.Cryptography.Encoding":"(,4.3.32767]","System.Security.Cryptography.OpenSsl":"(,5.0.32767]","System.Security.Cryptography.Primitives":"(,4.3.32767]","System.Security.Cryptography.X509Certificates":"(,4.3.32767]","System.Security.Principal":"(,4.3.32767]","System.Security.Principal.Windows":"(,5.0.32767]","System.Security.SecureString":"(,4.3.32767]","System.Text.Encoding":"(,4.3.32767]","System.Text.Encoding.CodePages":"(,10.0.32767]","System.Text.Encoding.Extensions":"(,4.3.32767]","System.Text.Encodings.Web":"(,10.0.32767]","System.Text.Json":"(,10.0.32767]","System.Text.RegularExpressions":"(,4.3.32767]","System.Threading":"(,4.3.32767]","System.Threading.AccessControl":"(,10.0.32767]","System.Threading.Channels":"(,10.0.32767]","System.Threading.Overlapped":"(,4.3.32767]","System.Threading.Tasks":"(,4.3.32767]","System.Threading.Tasks.Dataflow":"(,10.0.32767]","System.Threading.Tasks.Extensions":"(,5.0.32767]","System.Threading.Tasks.Parallel":"(,4.3.32767]","System.Threading.Thread":"(,4.3.32767]","System.Threading.ThreadPool":"(,4.3.32767]","System.Threading.Timer":"(,4.3.32767]","System.ValueTuple":"(,4.5.32767]","System.Xml.ReaderWriter":"(,4.3.32767]","System.Xml.XDocument":"(,4.3.32767]","System.Xml.XmlDocument":"(,4.3.32767]","System.Xml.XmlSerializer":"(,4.3.32767]","System.Xml.XPath":"(,4.3.32767]","System.Xml.XPath.XDocument":"(,5.0.32767]"}}} \ No newline at end of file diff --git a/Jellyfin.Api/obj/rider.project.model.nuget.info b/Jellyfin.Api/obj/rider.project.model.nuget.info new file mode 100644 index 00000000..c9990fe4 --- /dev/null +++ b/Jellyfin.Api/obj/rider.project.model.nuget.info @@ -0,0 +1 @@ +17714532052200000 \ No newline at end of file diff --git a/Jellyfin.Api/obj/rider.project.restore.info b/Jellyfin.Api/obj/rider.project.restore.info new file mode 100644 index 00000000..0a24497c --- /dev/null +++ b/Jellyfin.Api/obj/rider.project.restore.info @@ -0,0 +1 @@ +17715044201300000 \ No newline at end of file diff --git a/Jellyfin.Data/Attributes/OpenApiIgnoreEnumAttribute.cs b/Jellyfin.Data/Attributes/OpenApiIgnoreEnumAttribute.cs new file mode 100644 index 00000000..ff613d9f --- /dev/null +++ b/Jellyfin.Data/Attributes/OpenApiIgnoreEnumAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace Jellyfin.Data.Attributes; + +/// <summary> +/// Attribute to specify that the enum value is to be ignored when generating the openapi spec. +/// </summary> +[AttributeUsage(AttributeTargets.Field)] +public sealed class OpenApiIgnoreEnumAttribute : Attribute +{ +} diff --git a/Jellyfin.Data/DayOfWeekHelper.cs b/Jellyfin.Data/DayOfWeekHelper.cs new file mode 100644 index 00000000..836860e0 --- /dev/null +++ b/Jellyfin.Data/DayOfWeekHelper.cs @@ -0,0 +1,32 @@ +#pragma warning disable CS1591 + +using System; +using Jellyfin.Database.Implementations.Enums; + +namespace Jellyfin.Data +{ + public static class DayOfWeekHelper + { + public static DayOfWeek[] GetDaysOfWeek(DynamicDayOfWeek day) + { + return day switch + { + DynamicDayOfWeek.Everyday => new[] { DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday, DayOfWeek.Saturday }, + DynamicDayOfWeek.Weekday => new[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday }, + DynamicDayOfWeek.Weekend => new[] { DayOfWeek.Sunday, DayOfWeek.Saturday }, + _ => new[] { (DayOfWeek)day } + }; + } + + public static bool Contains(this DynamicDayOfWeek dynamicDayOfWeek, DayOfWeek dayOfWeek) + { + return dynamicDayOfWeek switch + { + DynamicDayOfWeek.Everyday => true, + DynamicDayOfWeek.Weekday => dayOfWeek is >= DayOfWeek.Monday and <= DayOfWeek.Friday, + DynamicDayOfWeek.Weekend => dayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday, + _ => (DayOfWeek)dynamicDayOfWeek == dayOfWeek + }; + } + } +} diff --git a/Jellyfin.Data/Dtos/DeviceOptionsDto.cs b/Jellyfin.Data/Dtos/DeviceOptionsDto.cs new file mode 100644 index 00000000..aad57870 --- /dev/null +++ b/Jellyfin.Data/Dtos/DeviceOptionsDto.cs @@ -0,0 +1,22 @@ +namespace Jellyfin.Data.Dtos; + +/// <summary> +/// A dto representing custom options for a device. +/// </summary> +public class DeviceOptionsDto +{ + /// <summary> + /// Gets or sets the id. + /// </summary> + public int Id { get; set; } + + /// <summary> + /// Gets or sets the device id. + /// </summary> + public string? DeviceId { get; set; } + + /// <summary> + /// Gets or sets the custom name. + /// </summary> + public string? CustomName { get; set; } +} diff --git a/Jellyfin.Data/Enums/ActivityLogSortBy.cs b/Jellyfin.Data/Enums/ActivityLogSortBy.cs new file mode 100644 index 00000000..d6d44e8c --- /dev/null +++ b/Jellyfin.Data/Enums/ActivityLogSortBy.cs @@ -0,0 +1,49 @@ +namespace Jellyfin.Data.Enums; + +/// <summary> +/// Activity log sorting options. +/// </summary> +public enum ActivityLogSortBy +{ + /// <summary> + /// Sort by name. + /// </summary> + Name = 0, + + /// <summary> + /// Sort by overview. + /// </summary> + Overiew = 1, + + /// <summary> + /// Sort by short overview. + /// </summary> + ShortOverview = 2, + + /// <summary> + /// Sort by type. + /// </summary> + Type = 3, + + /* + /// <summary> + /// Sort by item name. + /// </summary> + Item = 4, + */ + + /// <summary> + /// Sort by date. + /// </summary> + DateCreated = 5, + + /// <summary> + /// Sort by username. + /// </summary> + Username = 6, + + /// <summary> + /// Sort by severity. + /// </summary> + LogSeverity = 7 +} diff --git a/Jellyfin.Data/Enums/AudioSpatialFormat.cs b/Jellyfin.Data/Enums/AudioSpatialFormat.cs new file mode 100644 index 00000000..5e3a1233 --- /dev/null +++ b/Jellyfin.Data/Enums/AudioSpatialFormat.cs @@ -0,0 +1,22 @@ +namespace Jellyfin.Data.Enums; + +/// <summary> +/// An enum representing formats of spatial audio. +/// </summary> +public enum AudioSpatialFormat +{ + /// <summary> + /// None audio spatial format. + /// </summary> + None, + + /// <summary> + /// Dolby Atmos audio spatial format. + /// </summary> + DolbyAtmos, + + /// <summary> + /// DTS:X audio spatial format. + /// </summary> + DTSX, +} diff --git a/Jellyfin.Data/Enums/BaseItemKind.cs b/Jellyfin.Data/Enums/BaseItemKind.cs new file mode 100644 index 00000000..a0286c31 --- /dev/null +++ b/Jellyfin.Data/Enums/BaseItemKind.cs @@ -0,0 +1,205 @@ +namespace Jellyfin.Data.Enums +{ + /// <summary> + /// The base item kind. + /// </summary> + /// <remarks> + /// This enum is generated from all classes that inherit from <c>BaseItem</c>. + /// </remarks> + public enum BaseItemKind + { + /// <summary> + /// Item is aggregate folder. + /// </summary> + AggregateFolder, + + /// <summary> + /// Item is audio. + /// </summary> + Audio, + + /// <summary> + /// Item is audio book. + /// </summary> + AudioBook, + + /// <summary> + /// Item is base plugin folder. + /// </summary> + BasePluginFolder, + + /// <summary> + /// Item is book. + /// </summary> + Book, + + /// <summary> + /// Item is box set. + /// </summary> + BoxSet, + + /// <summary> + /// Item is channel. + /// </summary> + Channel, + + /// <summary> + /// Item is channel folder item. + /// </summary> + ChannelFolderItem, + + /// <summary> + /// Item is collection folder. + /// </summary> + CollectionFolder, + + /// <summary> + /// Item is episode. + /// </summary> + Episode, + + /// <summary> + /// Item is folder. + /// </summary> + Folder, + + /// <summary> + /// Item is genre. + /// </summary> + Genre, + + /// <summary> + /// Item is manual playlists folder. + /// </summary> + ManualPlaylistsFolder, + + /// <summary> + /// Item is movie. + /// </summary> + Movie, + + /// <summary> + /// Item is a live tv channel. + /// </summary> + LiveTvChannel, + + /// <summary> + /// Item is a live tv program. + /// </summary> + LiveTvProgram, + + /// <summary> + /// Item is music album. + /// </summary> + MusicAlbum, + + /// <summary> + /// Item is music artist. + /// </summary> + MusicArtist, + + /// <summary> + /// Item is music genre. + /// </summary> + MusicGenre, + + /// <summary> + /// Item is music video. + /// </summary> + MusicVideo, + + /// <summary> + /// Item is person. + /// </summary> + Person, + + /// <summary> + /// Item is photo. + /// </summary> + Photo, + + /// <summary> + /// Item is photo album. + /// </summary> + PhotoAlbum, + + /// <summary> + /// Item is playlist. + /// </summary> + Playlist, + + /// <summary> + /// Item is playlist folder. + /// </summary> + PlaylistsFolder, + + /// <summary> + /// Item is program. + /// </summary> + Program, + + /// <summary> + /// Item is recording. + /// </summary> + /// <remarks> + /// Manually added. + /// </remarks> + Recording, + + /// <summary> + /// Item is season. + /// </summary> + Season, + + /// <summary> + /// Item is series. + /// </summary> + Series, + + /// <summary> + /// Item is studio. + /// </summary> + Studio, + + /// <summary> + /// Item is trailer. + /// </summary> + Trailer, + + /// <summary> + /// Item is live tv channel. + /// </summary> + /// <remarks> + /// Type is overridden. + /// </remarks> + TvChannel, + + /// <summary> + /// Item is live tv program. + /// </summary> + /// <remarks> + /// Type is overridden. + /// </remarks> + TvProgram, + + /// <summary> + /// Item is user root folder. + /// </summary> + UserRootFolder, + + /// <summary> + /// Item is user view. + /// </summary> + UserView, + + /// <summary> + /// Item is video. + /// </summary> + Video, + + /// <summary> + /// Item is year. + /// </summary> + Year + } +} diff --git a/Jellyfin.Data/Enums/CollectionType.cs b/Jellyfin.Data/Enums/CollectionType.cs new file mode 100644 index 00000000..e3d3b07a --- /dev/null +++ b/Jellyfin.Data/Enums/CollectionType.cs @@ -0,0 +1,165 @@ +#pragma warning disable SA1300 // The name of a C# element does not begin with an upper-case letter. - disabled due to legacy requirement. +using Jellyfin.Data.Attributes; + +namespace Jellyfin.Data.Enums; + +/// <summary> +/// Collection type. +/// </summary> +public enum CollectionType +{ + /// <summary> + /// Unknown collection. + /// </summary> + unknown = 0, + + /// <summary> + /// Movies collection. + /// </summary> + movies = 1, + + /// <summary> + /// Tv shows collection. + /// </summary> + tvshows = 2, + + /// <summary> + /// Music collection. + /// </summary> + music = 3, + + /// <summary> + /// Music videos collection. + /// </summary> + musicvideos = 4, + + /// <summary> + /// Trailers collection. + /// </summary> + trailers = 5, + + /// <summary> + /// Home videos collection. + /// </summary> + homevideos = 6, + + /// <summary> + /// Box sets collection. + /// </summary> + boxsets = 7, + + /// <summary> + /// Books collection. + /// </summary> + books = 8, + + /// <summary> + /// Photos collection. + /// </summary> + photos = 9, + + /// <summary> + /// Live tv collection. + /// </summary> + livetv = 10, + + /// <summary> + /// Playlists collection. + /// </summary> + playlists = 11, + + /// <summary> + /// Folders collection. + /// </summary> + folders = 12, + + /// <summary> + /// Tv show series collection. + /// </summary> + [OpenApiIgnoreEnum] + tvshowseries = 101, + + /// <summary> + /// Tv genres collection. + /// </summary> + [OpenApiIgnoreEnum] + tvgenres = 102, + + /// <summary> + /// Tv genre collection. + /// </summary> + [OpenApiIgnoreEnum] + tvgenre = 103, + + /// <summary> + /// Tv latest collection. + /// </summary> + [OpenApiIgnoreEnum] + tvlatest = 104, + + /// <summary> + /// Tv next up collection. + /// </summary> + [OpenApiIgnoreEnum] + tvnextup = 105, + + /// <summary> + /// Tv resume collection. + /// </summary> + [OpenApiIgnoreEnum] + tvresume = 106, + + /// <summary> + /// Tv favorite series collection. + /// </summary> + [OpenApiIgnoreEnum] + tvfavoriteseries = 107, + + /// <summary> + /// Tv favorite episodes collection. + /// </summary> + [OpenApiIgnoreEnum] + tvfavoriteepisodes = 108, + + /// <summary> + /// Latest movies collection. + /// </summary> + [OpenApiIgnoreEnum] + movielatest = 109, + + /// <summary> + /// Movies to resume collection. + /// </summary> + [OpenApiIgnoreEnum] + movieresume = 110, + + /// <summary> + /// Movie movie collection. + /// </summary> + [OpenApiIgnoreEnum] + moviemovies = 111, + + /// <summary> + /// Movie collections collection. + /// </summary> + [OpenApiIgnoreEnum] + moviecollection = 112, + + /// <summary> + /// Movie favorites collection. + /// </summary> + [OpenApiIgnoreEnum] + moviefavorites = 113, + + /// <summary> + /// Movie genres collection. + /// </summary> + [OpenApiIgnoreEnum] + moviegenres = 114, + + /// <summary> + /// Movie genre collection. + /// </summary> + [OpenApiIgnoreEnum] + moviegenre = 115 +} diff --git a/Jellyfin.Data/Enums/ItemSortBy.cs b/Jellyfin.Data/Enums/ItemSortBy.cs new file mode 100644 index 00000000..ef765029 --- /dev/null +++ b/Jellyfin.Data/Enums/ItemSortBy.cs @@ -0,0 +1,157 @@ +namespace Jellyfin.Data.Enums; + +/// <summary> +/// These represent sort orders. +/// </summary> +public enum ItemSortBy +{ + /// <summary> + /// Default sort order. + /// </summary> + Default = 0, + + /// <summary> + /// The aired episode order. + /// </summary> + AiredEpisodeOrder = 1, + + /// <summary> + /// The album. + /// </summary> + Album = 2, + + /// <summary> + /// The album artist. + /// </summary> + AlbumArtist = 3, + + /// <summary> + /// The artist. + /// </summary> + Artist = 4, + + /// <summary> + /// The date created. + /// </summary> + DateCreated = 5, + + /// <summary> + /// The official rating. + /// </summary> + OfficialRating = 6, + + /// <summary> + /// The date played. + /// </summary> + DatePlayed = 7, + + /// <summary> + /// The premiere date. + /// </summary> + PremiereDate = 8, + + /// <summary> + /// The start date. + /// </summary> + StartDate = 9, + + /// <summary> + /// The sort name. + /// </summary> + SortName = 10, + + /// <summary> + /// The name. + /// </summary> + Name = 11, + + /// <summary> + /// The random. + /// </summary> + Random = 12, + + /// <summary> + /// The runtime. + /// </summary> + Runtime = 13, + + /// <summary> + /// The community rating. + /// </summary> + CommunityRating = 14, + + /// <summary> + /// The production year. + /// </summary> + ProductionYear = 15, + + /// <summary> + /// The play count. + /// </summary> + PlayCount = 16, + + /// <summary> + /// The critic rating. + /// </summary> + CriticRating = 17, + + /// <summary> + /// The IsFolder boolean. + /// </summary> + IsFolder = 18, + + /// <summary> + /// The IsUnplayed boolean. + /// </summary> + IsUnplayed = 19, + + /// <summary> + /// The IsPlayed boolean. + /// </summary> + IsPlayed = 20, + + /// <summary> + /// The series sort. + /// </summary> + SeriesSortName = 21, + + /// <summary> + /// The video bitrate. + /// </summary> + VideoBitRate = 22, + + /// <summary> + /// The air time. + /// </summary> + AirTime = 23, + + /// <summary> + /// The studio. + /// </summary> + Studio = 24, + + /// <summary> + /// The IsFavouriteOrLiked boolean. + /// </summary> + IsFavoriteOrLiked = 25, + + /// <summary> + /// The last content added date. + /// </summary> + DateLastContentAdded = 26, + + /// <summary> + /// The series last played date. + /// </summary> + SeriesDatePlayed = 27, + + /// <summary> + /// The parent index number. + /// </summary> + ParentIndexNumber = 28, + + /// <summary> + /// The index number. + /// </summary> + IndexNumber = 29, +} diff --git a/Jellyfin.Data/Enums/MediaStreamProtocol.cs b/Jellyfin.Data/Enums/MediaStreamProtocol.cs new file mode 100644 index 00000000..844dc95c --- /dev/null +++ b/Jellyfin.Data/Enums/MediaStreamProtocol.cs @@ -0,0 +1,22 @@ +#pragma warning disable SA1300 // Lowercase required for backwards compat. +using System.ComponentModel; + +namespace Jellyfin.Data.Enums; + +/// <summary> +/// Media streaming protocol. +/// Lowercase for backwards compatibility. +/// </summary> +[DefaultValue(http)] +public enum MediaStreamProtocol +{ + /// <summary> + /// HTTP. + /// </summary> + http = 0, + + /// <summary> + /// HTTP Live Streaming. + /// </summary> + hls = 1 +} diff --git a/Jellyfin.Data/Enums/MediaType.cs b/Jellyfin.Data/Enums/MediaType.cs new file mode 100644 index 00000000..3e8471ea --- /dev/null +++ b/Jellyfin.Data/Enums/MediaType.cs @@ -0,0 +1,32 @@ +namespace Jellyfin.Data.Enums; + +/// <summary> +/// Media types. +/// </summary> +public enum MediaType +{ + /// <summary> + /// Unknown media type. + /// </summary> + Unknown = 0, + + /// <summary> + /// Video media. + /// </summary> + Video = 1, + + /// <summary> + /// Audio media. + /// </summary> + Audio = 2, + + /// <summary> + /// Photo media. + /// </summary> + Photo = 3, + + /// <summary> + /// Book media. + /// </summary> + Book = 4 +} diff --git a/Jellyfin.Data/Enums/PersonKind.cs b/Jellyfin.Data/Enums/PersonKind.cs new file mode 100644 index 00000000..29308789 --- /dev/null +++ b/Jellyfin.Data/Enums/PersonKind.cs @@ -0,0 +1,133 @@ +namespace Jellyfin.Data.Enums; + +/// <summary> +/// The person kind. +/// </summary> +public enum PersonKind +{ + /// <summary> + /// An unknown person kind. + /// </summary> + Unknown, + + /// <summary> + /// A person whose profession is acting on the stage, in films, or on television. + /// </summary> + Actor, + + /// <summary> + /// A person who supervises the actors and other staff in a film, play, or similar production. + /// </summary> + Director, + + /// <summary> + /// A person who writes music, especially as a professional occupation. + /// </summary> + Composer, + + /// <summary> + /// A writer of a book, article, or document. Can also be used as a generic term for music writer if there is a lack of specificity. + /// </summary> + Writer, + + /// <summary> + /// A well-known actor or other performer who appears in a work in which they do not have a regular role. + /// </summary> + GuestStar, + + /// <summary> + /// A person responsible for the financial and managerial aspects of the making of a film or broadcast or for staging a play, opera, etc. + /// </summary> + Producer, + + /// <summary> + /// A person who directs the performance of an orchestra or choir. + /// </summary> + Conductor, + + /// <summary> + /// A person who writes the words to a song or musical. + /// </summary> + Lyricist, + + /// <summary> + /// A person who adapts a musical composition for performance. + /// </summary> + Arranger, + + /// <summary> + /// An audio engineer who performed a general engineering role. + /// </summary> + Engineer, + + /// <summary> + /// An engineer responsible for using a mixing console to mix a recorded track into a single piece of music suitable for release. + /// </summary> + Mixer, + + /// <summary> + /// A person who remixed a recording by taking one or more other tracks, substantially altering them and mixing them together with other material. + /// </summary> + Remixer, + + /// <summary> + /// A person who created the material. + /// </summary> + Creator, + + /// <summary> + /// A person who was the artist. + /// </summary> + Artist, + + /// <summary> + /// A person who was the album artist. + /// </summary> + AlbumArtist, + + /// <summary> + /// A person who was the author. + /// </summary> + Author, + + /// <summary> + /// A person who was the illustrator. + /// </summary> + Illustrator, + + /// <summary> + /// A person responsible for drawing the art. + /// </summary> + Penciller, + + /// <summary> + /// A person responsible for inking the pencil art. + /// </summary> + Inker, + + /// <summary> + /// A person responsible for applying color to drawings. + /// </summary> + Colorist, + + /// <summary> + /// A person responsible for drawing text and speech bubbles. + /// </summary> + Letterer, + + /// <summary> + /// A person responsible for drawing the cover art. + /// </summary> + CoverArtist, + + /// <summary> + /// A person contributing to a resource by revising or elucidating the content, e.g., adding an introduction, notes, or other critical matter. + /// An editor may also prepare a resource for production, publication, or distribution. + /// </summary> + Editor, + + /// <summary> + /// A person who renders a text from one language into another. + /// </summary> + Translator +} diff --git a/Jellyfin.Data/Enums/SyncPlayAccessRequirementType.cs b/Jellyfin.Data/Enums/SyncPlayAccessRequirementType.cs new file mode 100644 index 00000000..8c3e6cb1 --- /dev/null +++ b/Jellyfin.Data/Enums/SyncPlayAccessRequirementType.cs @@ -0,0 +1,28 @@ +namespace Jellyfin.Data.Enums +{ + /// <summary> + /// Enum SyncPlayAccessRequirementType. + /// </summary> + public enum SyncPlayAccessRequirementType + { + /// <summary> + /// User must have access to SyncPlay, in some form. + /// </summary> + HasAccess = 0, + + /// <summary> + /// User must be able to create groups. + /// </summary> + CreateGroup = 1, + + /// <summary> + /// User must be able to join groups. + /// </summary> + JoinGroup = 2, + + /// <summary> + /// User must be in a group. + /// </summary> + IsInGroup = 3 + } +} diff --git a/Jellyfin.Data/Enums/UnratedItem.cs b/Jellyfin.Data/Enums/UnratedItem.cs new file mode 100644 index 00000000..21ec65af --- /dev/null +++ b/Jellyfin.Data/Enums/UnratedItem.cs @@ -0,0 +1,53 @@ +namespace Jellyfin.Data.Enums +{ + /// <summary> + /// An enum representing an unrated item. + /// </summary> + public enum UnratedItem + { + /// <summary> + /// A movie. + /// </summary> + Movie = 0, + + /// <summary> + /// A trailer. + /// </summary> + Trailer = 1, + + /// <summary> + /// A series. + /// </summary> + Series = 2, + + /// <summary> + /// Music. + /// </summary> + Music = 3, + + /// <summary> + /// A book. + /// </summary> + Book = 4, + + /// <summary> + /// A live TV channel. + /// </summary> + LiveTvChannel = 5, + + /// <summary> + /// A live TV program. + /// </summary> + LiveTvProgram = 6, + + /// <summary> + /// Channel content. + /// </summary> + ChannelContent = 7, + + /// <summary> + /// Another type, not covered by the other fields. + /// </summary> + Other = 8 + } +} diff --git a/Jellyfin.Data/Enums/VideoRange.cs b/Jellyfin.Data/Enums/VideoRange.cs new file mode 100644 index 00000000..5072e5ba --- /dev/null +++ b/Jellyfin.Data/Enums/VideoRange.cs @@ -0,0 +1,22 @@ +namespace Jellyfin.Data.Enums; + +/// <summary> +/// An enum representing video ranges. +/// </summary> +public enum VideoRange +{ + /// <summary> + /// Unknown video range. + /// </summary> + Unknown, + + /// <summary> + /// SDR video range. + /// </summary> + SDR, + + /// <summary> + /// HDR video range. + /// </summary> + HDR +} diff --git a/Jellyfin.Data/Enums/VideoRangeType.cs b/Jellyfin.Data/Enums/VideoRangeType.cs new file mode 100644 index 00000000..ce232d73 --- /dev/null +++ b/Jellyfin.Data/Enums/VideoRangeType.cs @@ -0,0 +1,73 @@ +namespace Jellyfin.Data.Enums; + +/// <summary> +/// An enum representing types of video ranges. +/// </summary> +public enum VideoRangeType +{ + /// <summary> + /// Unknown video range type. + /// </summary> + Unknown, + + /// <summary> + /// SDR video range type (8bit). + /// </summary> + SDR, + + /// <summary> + /// HDR10 video range type (10bit). + /// </summary> + HDR10, + + /// <summary> + /// HLG video range type (10bit). + /// </summary> + HLG, + + /// <summary> + /// Dolby Vision video range type (10bit encoded / 12bit remapped). + /// </summary> + DOVI, + + /// <summary> + /// Dolby Vision with HDR10 video range fallback (10bit). + /// </summary> + DOVIWithHDR10, + + /// <summary> + /// Dolby Vision with HLG video range fallback (10bit). + /// </summary> + DOVIWithHLG, + + /// <summary> + /// Dolby Vision with SDR video range fallback (8bit / 10bit). + /// </summary> + DOVIWithSDR, + + /// <summary> + /// Dolby Vision with Enhancment Layer (Profile 7). + /// </summary> + DOVIWithEL, + + /// <summary> + /// Dolby Vision and HDR10+ Metadata coexists. + /// </summary> + DOVIWithHDR10Plus, + + /// <summary> + /// Dolby Vision with Enhancment Layer (Profile 7) and HDR10+ Metadata coexists. + /// </summary> + DOVIWithELHDR10Plus, + + /// <summary> + /// Dolby Vision with invalid configuration. e.g. Profile 8 compat id 6. + /// When using this range, the server would assume the video is still HDR10 after removing the Dolby Vision metadata. + /// </summary> + DOVIInvalid, + + /// <summary> + /// HDR10+ video range type (10bit to 16bit). + /// </summary> + HDR10Plus +} diff --git a/Jellyfin.Data/Events/GenericEventArgs.cs b/Jellyfin.Data/Events/GenericEventArgs.cs new file mode 100644 index 00000000..7b9a5111 --- /dev/null +++ b/Jellyfin.Data/Events/GenericEventArgs.cs @@ -0,0 +1,26 @@ +using System; + +namespace Jellyfin.Data.Events +{ + /// <summary> + /// Provides a generic EventArgs subclass that can hold any kind of object. + /// </summary> + /// <typeparam name="T">The type of this event.</typeparam> + public class GenericEventArgs<T> : EventArgs + { + /// <summary> + /// Initializes a new instance of the <see cref="GenericEventArgs{T}"/> class. + /// </summary> + /// <param name="arg">The argument.</param> + public GenericEventArgs(T arg) + { + Argument = arg; + } + + /// <summary> + /// Gets the argument. + /// </summary> + /// <value>The argument.</value> + public T Argument { get; } + } +} diff --git a/Jellyfin.Data/Events/System/PendingRestartEventArgs.cs b/Jellyfin.Data/Events/System/PendingRestartEventArgs.cs new file mode 100644 index 00000000..ef16f27c --- /dev/null +++ b/Jellyfin.Data/Events/System/PendingRestartEventArgs.cs @@ -0,0 +1,11 @@ +using System; + +namespace Jellyfin.Data.Events.System +{ + /// <summary> + /// An event that occurs when there is a pending restart. + /// </summary> + public class PendingRestartEventArgs : EventArgs + { + } +} diff --git a/Jellyfin.Data/Events/Users/UserCreatedEventArgs.cs b/Jellyfin.Data/Events/Users/UserCreatedEventArgs.cs new file mode 100644 index 00000000..8de34fec --- /dev/null +++ b/Jellyfin.Data/Events/Users/UserCreatedEventArgs.cs @@ -0,0 +1,18 @@ +using Jellyfin.Database.Implementations.Entities; + +namespace Jellyfin.Data.Events.Users +{ + /// <summary> + /// An event that occurs when a user is created. + /// </summary> + public class UserCreatedEventArgs : GenericEventArgs<User> + { + /// <summary> + /// Initializes a new instance of the <see cref="UserCreatedEventArgs"/> class. + /// </summary> + /// <param name="arg">The user.</param> + public UserCreatedEventArgs(User arg) : base(arg) + { + } + } +} diff --git a/Jellyfin.Data/Events/Users/UserDeletedEventArgs.cs b/Jellyfin.Data/Events/Users/UserDeletedEventArgs.cs new file mode 100644 index 00000000..c85de34d --- /dev/null +++ b/Jellyfin.Data/Events/Users/UserDeletedEventArgs.cs @@ -0,0 +1,18 @@ +using Jellyfin.Database.Implementations.Entities; + +namespace Jellyfin.Data.Events.Users +{ + /// <summary> + /// An event that occurs when a user is deleted. + /// </summary> + public class UserDeletedEventArgs : GenericEventArgs<User> + { + /// <summary> + /// Initializes a new instance of the <see cref="UserDeletedEventArgs"/> class. + /// </summary> + /// <param name="arg">The user.</param> + public UserDeletedEventArgs(User arg) : base(arg) + { + } + } +} diff --git a/Jellyfin.Data/Events/Users/UserLockedOutEventArgs.cs b/Jellyfin.Data/Events/Users/UserLockedOutEventArgs.cs new file mode 100644 index 00000000..46b399d2 --- /dev/null +++ b/Jellyfin.Data/Events/Users/UserLockedOutEventArgs.cs @@ -0,0 +1,18 @@ +using Jellyfin.Database.Implementations.Entities; + +namespace Jellyfin.Data.Events.Users +{ + /// <summary> + /// An event that occurs when a user is locked out. + /// </summary> + public class UserLockedOutEventArgs : GenericEventArgs<User> + { + /// <summary> + /// Initializes a new instance of the <see cref="UserLockedOutEventArgs"/> class. + /// </summary> + /// <param name="arg">The user.</param> + public UserLockedOutEventArgs(User arg) : base(arg) + { + } + } +} diff --git a/Jellyfin.Data/Events/Users/UserPasswordChangedEventArgs.cs b/Jellyfin.Data/Events/Users/UserPasswordChangedEventArgs.cs new file mode 100644 index 00000000..ee41147d --- /dev/null +++ b/Jellyfin.Data/Events/Users/UserPasswordChangedEventArgs.cs @@ -0,0 +1,18 @@ +using Jellyfin.Database.Implementations.Entities; + +namespace Jellyfin.Data.Events.Users +{ + /// <summary> + /// An event that occurs when a user's password has changed. + /// </summary> + public class UserPasswordChangedEventArgs : GenericEventArgs<User> + { + /// <summary> + /// Initializes a new instance of the <see cref="UserPasswordChangedEventArgs"/> class. + /// </summary> + /// <param name="arg">The user.</param> + public UserPasswordChangedEventArgs(User arg) : base(arg) + { + } + } +} diff --git a/Jellyfin.Data/Events/Users/UserUpdatedEventArgs.cs b/Jellyfin.Data/Events/Users/UserUpdatedEventArgs.cs new file mode 100644 index 00000000..0f2763f3 --- /dev/null +++ b/Jellyfin.Data/Events/Users/UserUpdatedEventArgs.cs @@ -0,0 +1,18 @@ +using Jellyfin.Database.Implementations.Entities; + +namespace Jellyfin.Data.Events.Users +{ + /// <summary> + /// An event that occurs when a user is updated. + /// </summary> + public class UserUpdatedEventArgs : GenericEventArgs<User> + { + /// <summary> + /// Initializes a new instance of the <see cref="UserUpdatedEventArgs"/> class. + /// </summary> + /// <param name="arg">The user.</param> + public UserUpdatedEventArgs(User arg) : base(arg) + { + } + } +} diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj new file mode 100644 index 00000000..f7660f35 --- /dev/null +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -0,0 +1,53 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net10.0</TargetFramework> + <GenerateAssemblyInfo>false</GenerateAssemblyInfo> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + <PublishRepositoryUrl>true</PublishRepositoryUrl> + <EmbedUntrackedSources>true</EmbedUntrackedSources> + <IncludeSymbols>true</IncludeSymbols> + <SymbolPackageFormat>snupkg</SymbolPackageFormat> + </PropertyGroup> + + <PropertyGroup Condition=" '$(Stability)'=='Unstable'"> + <!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. --> + <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder> + </PropertyGroup> + + <PropertyGroup> + <Authors>Jellyfin Contributors</Authors> + <PackageId>Jellyfin.Data</PackageId> + <VersionPrefix>10.12.0</VersionPrefix> + <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> + <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> + </PropertyGroup> + + <!-- Code Analyzers --> + <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="IDisposableAnalyzers"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> + <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\src\Jellyfin.Database\Jellyfin.Database.Implementations\Jellyfin.Database.Implementations.csproj" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.Extensions.Logging" /> + </ItemGroup> + + <ItemGroup> + <Compile Include="..\SharedVersion.cs" /> + </ItemGroup> + +</Project> diff --git a/Jellyfin.Data/Queries/ActivityLogQuery.cs b/Jellyfin.Data/Queries/ActivityLogQuery.cs new file mode 100644 index 00000000..6de6c4c2 --- /dev/null +++ b/Jellyfin.Data/Queries/ActivityLogQuery.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Data.Queries; + +/// <summary> +/// A class representing a query to the activity logs. +/// </summary> +public class ActivityLogQuery : PaginatedQuery +{ + /// <summary> + /// Gets or sets a value indicating whether to take entries with a user id. + /// </summary> + public bool? HasUserId { get; set; } + + /// <summary> + /// Gets or sets the minimum date to query for. + /// </summary> + public DateTime? MinDate { get; set; } + + /// <summary> + /// Gets or sets the maximum date to query for. + /// </summary> + public DateTime? MaxDate { get; set; } + + /// <summary> + /// Gets or sets the name filter. + /// </summary> + public string? Name { get; set; } + + /// <summary> + /// Gets or sets the overview filter. + /// </summary> + public string? Overview { get; set; } + + /// <summary> + /// Gets or sets the short overview filter. + /// </summary> + public string? ShortOverview { get; set; } + + /// <summary> + /// Gets or sets the type filter. + /// </summary> + public string? Type { get; set; } + + /// <summary> + /// Gets or sets the item filter. + /// </summary> + public Guid? ItemId { get; set; } + + /// <summary> + /// Gets or sets the username filter. + /// </summary> + public string? Username { get; set; } + + /// <summary> + /// Gets or sets the log level filter. + /// </summary> + public LogLevel? Severity { get; set; } + + /// <summary> + /// Gets or sets the result ordering. + /// </summary> + public IReadOnlyCollection<(ActivityLogSortBy, SortOrder)>? OrderBy { get; set; } +} diff --git a/Jellyfin.Data/Queries/DeviceQuery.cs b/Jellyfin.Data/Queries/DeviceQuery.cs new file mode 100644 index 00000000..083e0054 --- /dev/null +++ b/Jellyfin.Data/Queries/DeviceQuery.cs @@ -0,0 +1,25 @@ +using System; + +namespace Jellyfin.Data.Queries +{ + /// <summary> + /// A query to retrieve devices. + /// </summary> + public class DeviceQuery : PaginatedQuery + { + /// <summary> + /// Gets or sets the user id of the device. + /// </summary> + public Guid? UserId { get; set; } + + /// <summary> + /// Gets or sets the device id. + /// </summary> + public string? DeviceId { get; set; } + + /// <summary> + /// Gets or sets the access token. + /// </summary> + public string? AccessToken { get; set; } + } +} diff --git a/Jellyfin.Data/Queries/PaginatedQuery.cs b/Jellyfin.Data/Queries/PaginatedQuery.cs new file mode 100644 index 00000000..58267ebe --- /dev/null +++ b/Jellyfin.Data/Queries/PaginatedQuery.cs @@ -0,0 +1,18 @@ +namespace Jellyfin.Data.Queries +{ + /// <summary> + /// An abstract class for paginated queries. + /// </summary> + public abstract class PaginatedQuery + { + /// <summary> + /// Gets or sets the index to start at. + /// </summary> + public int? Skip { get; set; } + + /// <summary> + /// Gets or sets the maximum number of items to include. + /// </summary> + public int? Limit { get; set; } + } +} diff --git a/Jellyfin.Data/UserEntityExtensions.cs b/Jellyfin.Data/UserEntityExtensions.cs new file mode 100644 index 00000000..149fc904 --- /dev/null +++ b/Jellyfin.Data/UserEntityExtensions.cs @@ -0,0 +1,220 @@ +using System; +using System.ComponentModel; +using System.Linq; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Database.Implementations.Interfaces; + +namespace Jellyfin.Data; + +/// <summary> +/// Contains extension methods for manipulation of <see cref="User"/> entities. +/// </summary> +public static class UserEntityExtensions +{ + /// <summary> + /// The values being delimited here are Guids, so commas work as they do not appear in Guids. + /// </summary> + private const char Delimiter = ','; + + /// <summary> + /// Checks whether the user has the specified permission. + /// </summary> + /// <param name="entity">The entity to update.</param> + /// <param name="kind">The permission kind.</param> + /// <returns><c>True</c> if the user has the specified permission.</returns> + public static bool HasPermission(this IHasPermissions entity, PermissionKind kind) + { + return entity.Permissions.FirstOrDefault(p => p.Kind == kind)?.Value ?? false; + } + + /// <summary> + /// Sets the given permission kind to the provided value. + /// </summary> + /// <param name="entity">The entity to update.</param> + /// <param name="kind">The permission kind.</param> + /// <param name="value">The value to set.</param> + public static void SetPermission(this IHasPermissions entity, PermissionKind kind, bool value) + { + var currentPermission = entity.Permissions.FirstOrDefault(p => p.Kind == kind); + if (currentPermission is null) + { + entity.Permissions.Add(new Permission(kind, value)); + } + else + { + currentPermission.Value = value; + } + } + + /// <summary> + /// Gets the user's preferences for the given preference kind. + /// </summary> + /// <param name="entity">The entity to update.</param> + /// <param name="preference">The preference kind.</param> + /// <returns>A string array containing the user's preferences.</returns> + public static string[] GetPreference(this User entity, PreferenceKind preference) + { + var val = entity.Preferences.FirstOrDefault(p => p.Kind == preference)?.Value; + + return string.IsNullOrEmpty(val) ? Array.Empty<string>() : val.Split(Delimiter); + } + + /// <summary> + /// Gets the user's preferences for the given preference kind. + /// </summary> + /// <param name="entity">The entity to update.</param> + /// <param name="preference">The preference kind.</param> + /// <typeparam name="T">Type of preference.</typeparam> + /// <returns>A {T} array containing the user's preference.</returns> + public static T[] GetPreferenceValues<T>(this User entity, PreferenceKind preference) + { + var val = entity.Preferences.FirstOrDefault(p => p.Kind == preference)?.Value; + if (string.IsNullOrEmpty(val)) + { + return Array.Empty<T>(); + } + + // Convert array of {string} to array of {T} + var converter = TypeDescriptor.GetConverter(typeof(T)); + var stringValues = val.Split(Delimiter); + var convertedCount = 0; + var parsedValues = new T[stringValues.Length]; + for (var i = 0; i < stringValues.Length; i++) + { + try + { + var parsedValue = converter.ConvertFromString(stringValues[i].Trim()); + if (parsedValue is not null) + { + parsedValues[convertedCount++] = (T)parsedValue; + } + } + catch (FormatException) + { + // Unable to convert value + } + } + + return parsedValues[..convertedCount]; + } + + /// <summary> + /// Sets the specified preference to the given value. + /// </summary> + /// <param name="entity">The entity to update.</param> + /// <param name="preference">The preference kind.</param> + /// <param name="values">The values.</param> + public static void SetPreference(this User entity, PreferenceKind preference, string[] values) + { + var value = string.Join(Delimiter, values); + var currentPreference = entity.Preferences.FirstOrDefault(p => p.Kind == preference); + if (currentPreference is null) + { + entity.Preferences.Add(new Preference(preference, value)); + } + else + { + currentPreference.Value = value; + } + } + + /// <summary> + /// Sets the specified preference to the given value. + /// </summary> + /// <param name="entity">The entity to update.</param> + /// <param name="preference">The preference kind.</param> + /// <param name="values">The values.</param> + /// <typeparam name="T">The type of value.</typeparam> + public static void SetPreference<T>(this User entity, PreferenceKind preference, T[] values) + { + var value = string.Join(Delimiter, values); + var currentPreference = entity.Preferences.FirstOrDefault(p => p.Kind == preference); + if (currentPreference is null) + { + entity.Preferences.Add(new Preference(preference, value)); + } + else + { + currentPreference.Value = value; + } + } + + /// <summary> + /// Checks whether this user is currently allowed to use the server. + /// </summary> + /// <param name="entity">The entity to update.</param> + /// <returns><c>True</c> if the current time is within an access schedule, or there are no access schedules.</returns> + public static bool IsParentalScheduleAllowed(this User entity) + { + return entity.AccessSchedules.Count == 0 + || entity.AccessSchedules.Any(i => IsParentalScheduleAllowed(i, DateTime.UtcNow)); + } + + /// <summary> + /// Checks whether the provided folder is in this user's grouped folders. + /// </summary> + /// <param name="entity">The entity to update.</param> + /// <param name="id">The Guid of the folder.</param> + /// <returns><c>True</c> if the folder is in the user's grouped folders.</returns> + public static bool IsFolderGrouped(this User entity, Guid id) + { + return Array.IndexOf(GetPreferenceValues<Guid>(entity, PreferenceKind.GroupedFolders), id) != -1; + } + + /// <summary> + /// Initializes the default permissions for a user. Should only be called on user creation. + /// </summary> + /// <param name="entity">The entity to update.</param> + // TODO: make these user configurable? + public static void AddDefaultPermissions(this User entity) + { + entity.Permissions.Add(new Permission(PermissionKind.IsAdministrator, false)); + entity.Permissions.Add(new Permission(PermissionKind.IsDisabled, false)); + entity.Permissions.Add(new Permission(PermissionKind.IsHidden, true)); + entity.Permissions.Add(new Permission(PermissionKind.EnableAllChannels, true)); + entity.Permissions.Add(new Permission(PermissionKind.EnableAllDevices, true)); + entity.Permissions.Add(new Permission(PermissionKind.EnableAllFolders, true)); + entity.Permissions.Add(new Permission(PermissionKind.EnableContentDeletion, false)); + entity.Permissions.Add(new Permission(PermissionKind.EnableContentDownloading, true)); + entity.Permissions.Add(new Permission(PermissionKind.EnableMediaConversion, true)); + entity.Permissions.Add(new Permission(PermissionKind.EnableMediaPlayback, true)); + entity.Permissions.Add(new Permission(PermissionKind.EnablePlaybackRemuxing, true)); + entity.Permissions.Add(new Permission(PermissionKind.EnablePublicSharing, true)); + entity.Permissions.Add(new Permission(PermissionKind.EnableRemoteAccess, true)); + entity.Permissions.Add(new Permission(PermissionKind.EnableSyncTranscoding, true)); + entity.Permissions.Add(new Permission(PermissionKind.EnableAudioPlaybackTranscoding, true)); + entity.Permissions.Add(new Permission(PermissionKind.EnableLiveTvAccess, true)); + entity.Permissions.Add(new Permission(PermissionKind.EnableLiveTvManagement, true)); + entity.Permissions.Add(new Permission(PermissionKind.EnableSharedDeviceControl, true)); + entity.Permissions.Add(new Permission(PermissionKind.EnableVideoPlaybackTranscoding, true)); + entity.Permissions.Add(new Permission(PermissionKind.ForceRemoteSourceTranscoding, false)); + entity.Permissions.Add(new Permission(PermissionKind.EnableRemoteControlOfOtherUsers, false)); + entity.Permissions.Add(new Permission(PermissionKind.EnableCollectionManagement, false)); + entity.Permissions.Add(new Permission(PermissionKind.EnableSubtitleManagement, false)); + entity.Permissions.Add(new Permission(PermissionKind.EnableLyricManagement, false)); + } + + /// <summary> + /// Initializes the default preferences. Should only be called on user creation. + /// </summary> + /// <param name="entity">The entity to update.</param> + public static void AddDefaultPreferences(this User entity) + { + foreach (var val in Enum.GetValues<PreferenceKind>()) + { + entity.Preferences.Add(new Preference(val, string.Empty)); + } + } + + private static bool IsParentalScheduleAllowed(AccessSchedule schedule, DateTime date) + { + var localTime = date.ToLocalTime(); + var hour = localTime.TimeOfDay.TotalHours; + var currentDayOfWeek = localTime.DayOfWeek; + + return schedule.DayOfWeek.Contains(currentDayOfWeek) + && hour >= schedule.StartHour + && hour <= schedule.EndHour; + } +} diff --git a/Jellyfin.Data/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs b/Jellyfin.Data/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs new file mode 100644 index 00000000..925b1351 --- /dev/null +++ b/Jellyfin.Data/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// <autogenerated /> +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")] diff --git a/Jellyfin.Data/obj/Debug/net10.0/Jellyfin.Data.GeneratedMSBuildEditorConfig.editorconfig b/Jellyfin.Data/obj/Debug/net10.0/Jellyfin.Data.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 00000000..ef116c1e --- /dev/null +++ b/Jellyfin.Data/obj/Debug/net10.0/Jellyfin.Data.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,27 @@ +is_global = true +build_property.TargetFramework = net10.0 +build_property.TargetFramework = net10.0 +build_property.TargetFrameworkIdentifier = .NETCoreApp +build_property.TargetFrameworkIdentifier = .NETCoreApp +build_property.TargetFrameworkVersion = v10.0 +build_property.TargetFrameworkVersion = v10.0 +build_property.TargetPlatformMinVersion = +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = Jellyfin.Data +build_property.ProjectDir = /srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 10.0 +build_property.EnableCodeStyleSeverity = diff --git a/Jellyfin.Data/obj/Debug/net10.0/Jellyfin.Data.assets.cache b/Jellyfin.Data/obj/Debug/net10.0/Jellyfin.Data.assets.cache new file mode 100644 index 0000000000000000000000000000000000000000..01382368e59e51505a9a4452b2254aef24bad0c7 GIT binary patch literal 12114 zcmd5?-E$O05D$_Bf)W%E<vRf(fXQA$J_sluAs^=n!4yGX^ufKoNwToFd$qeqIDED& z$_K5oN_|oO0j=`y@WsEtDqnr{!6#qrzTLU$+w1+vCWKto<T7(T-M{IX>FMs7r8~u$ z*Sfm81|O{d_xVr8@58_R+W+s+KLd}7fB&`nH~q&ypB4WYdiwLJ&(3vq?E|_>Va=(E z!umtU7G6QK>#M>q)O6#Kz6y1-VtO^l)6167ZQW`-6|Ps9&_=cCf~r>>--b?xS18T3 z$Y&(ER<W#CKoEX*LBaobL-}S`qU5S+xQ^$n_?qEV0^y$NX=UBEMMbZfO8kU228dWZ zi!COzz@!NYncQwNnW>p9$nFqi4}5?B@<7fmujy`W16aS#idERY={Htfy(-on_mSba zLR0D-8-?i|Pf_Q5lf{B$F^eo%W%h>3+ymb~WCr-e7q(|Qwx?On>Z)n4YI@o8UET1p zu@DCl7L3)fC$tG%i)!xY6}!73yP~j#tNV`Y(dD_9fc-WHSf{pGd>;iIv}LF68X`mq zW2Pwk2+B_r_mC%C6BH=twlJ!VO~&k$<$zSqA(e+#0ypieP`*mmidWTLUyAdzYTbgB zw&n`G0&^(a3uD@tHa^C#uw`T*L3r9?8w1%+!J(-`aQmUGCo98mSi*2>YN=~=+w;-1 z0iw&W=qD$qcu?4L7J5nmo`&XR!F(dxmQR0(<p6wFw+{=kuM`1*=)$^_h(wuL#wy5- zW|`#zSa{rabXzet0I;BK$It6faAt7lzX9bSlxNAyClL&50<t5UAqE5&kc4>%U{RQd zq5kD&V7kK6(G2L;#?^Pf)t5qiGXn7l)KfMh(WEkdhHk9I+0qzAs?M`v=QusS6#@A+ z)W68O^pqHhu_~&L+ep!%>L$@GHoH;f9gW~R2KDbU&##hTIQELUT8Bde%`tZzrK_-w zMVum!M*yFIdiDjW09Qm!*cD+L4by%onkQ#wb<VZ=mSkV5(~}X1r=b2khfW!Y`83Pn zjnnVx2>1c0CtGWFGlh;xW|d0MgP5Md2$C~U&$F*XDEU+;amA^N`b22P&*oEC#5GFY zE7B1n>Y{!RB6$ajw{Z(obIq#hn~x#73H``aFiWj2bQeGX)pl~6hx+wIJT=F%8ii?X zN}DW%LZ{$JUBYI{c_`$30lsmh7oil>IYXr6igxzXE%r?pooMq?M8>;N#}A%`n;1o{ zQbrE=p`+Lm^n^D>A4m)S;!$~+i$d||i;G#qL8LyZsF`R7DJ~^aXmm#*`A`bE449Gj zLNcxL6OfpUi-bDzNQ}ZbOIWMZLQ)3@C~~Eq9-)gp!>|v9t@<$58Hp!ZAPjMbCQq<E zLraFNd5v(U4yk!|p#?&M)2uvY6_BQ<xr_0?eZhXl4TseOk<bd;&Si>okn-SiNAQ8o z$2*&~kHcsNf5))<7`G3C<PD}ahM{0SW&{d4!y1$Vlu;;SP%c9m$GRGPN)iQlV<tv2 z$2<x$$2!M+0^0e-7*14ZpCvy$E|DJw6db=vXg?BBb{O(X78uGR3k+pB7E{nZoM~>t zAXYNQFccZ@k70ZT+6O!5EC@VD%EH$%`jp(JLvB}NxLt#GoVt^VX95sxe*pNT+Di7( z0C1-Segqy3{IdJkOzLcl0zLwb27GxOLK#HqB<9WR?8uXVo*NngM+3c=7T^&)G{D<= zfgJ%#1H0@CP9396w3K;Lxl@%uqXFH{4&(@E8pv&907nqe04~oywGxussX+<n&Cq>H z1GgON$s4c{_0WKA+XFR%;~X4>GR3!v(1vi_^3aCxj)pe<fPTAkxadCOhaTY=h++=+ zJt*%(xenz9l$%g)LD?xsJjA#XK0V?QKIV2g_%qO+-6Fp@7l1&gE&xKuoG%CcHnit4 zJzMW05b)GRK=7Cg=D^QF`%7~n2wv(!AYRN7b9m>VedoCtWFW@@ptN*25X>EO1DS{R z><j+IxgZ2ObwLn1=9D?;3(&r^T@*4X>Z0JF24V(Pg!UX~bm%{1_5!8LKoC+F1`(c# zLHGf*=dm+e@8S^f)Wt#Yn49L-ObOa^s5h?*L=aLJ2oYk=nnU;@v~RnML~v3U32~l} z!FdPTH{n7NiPVKciJ0T&G;<f)w<LQ^WK$OlWn=D}lf4M-^m7j?{rBoc;V%Ubq)HVw z*?fr7d;(9V;L5P4`D%(JCR37Wy+JZ9YtpzkxXU2?Nh(<Gdm7RwCOxA$%iiGSNVc?P zZ}7e&DMi`WdT^1PW#7M~`)egT)b>bBOvJxK*g$svT6V@%g?ME{Gu{jim8hT2E<<%f z3H+3DS(>XcBGmcK^+FkZ8dg#VcLL*?xvNX~jou;LOqUUR{}7!GHEJgL5jPu&olru) zON;r`aWNR%u02pTzh%SCJ)9~+S^?mczEUJ{biJpGAvd>a_a^cZH2Sbe&4RjPK?MYN zEIrK&)WhL;+b&Sa)yvWF_s$#8?tAd?aZhNL50$)9wPu-y>06CCv0}pO(w+s~^2FZz z!gH+0qSF1qt&487L{}2??eTAA7ZdwcTep%%rkAd_GIA42jaHCo3LVAqL<an$!BTRc z4ZQnfZnXNF$3g2F<VLcrj(2>KTa>;T&lCQGMosK->tK_sq7NSLHSRiwZgu-Gmj~yB z=bLu(CC_rbj59l^F1eV?UR--xGDXKeNhWVM@gsy-O`0^nQP$KY=%iN0cbw;%Xh4VA sKAdqvE0UQwI)nkXr?ff1n%9UV$i|KsBK~}9H!gjxXBhqJ0vBige~In7umAu6 literal 0 HcmV?d00001 diff --git a/Jellyfin.Data/obj/Debug/net10.0/Jellyfin.Data.csproj.AssemblyReference.cache b/Jellyfin.Data/obj/Debug/net10.0/Jellyfin.Data.csproj.AssemblyReference.cache new file mode 100644 index 0000000000000000000000000000000000000000..754d3ef15d673ff65db098d0b80fc46c30f17cc6 GIT binary patch literal 7025 zcmZQ$<YQo9V9eLg$j?pHFVD))OD)#d%PUP!EzvJXOwLYB2l8_>lZ*0;^V3T7Qu9hO zODfZf5_40_^NX^R^NUjT4Gr`R^o;d$GL!W4QcFNQeP6f+SFi>*m<DH{2ECM=97aZ< ztqg}06l`MdaWFD6%Hq|*$OTdj1k4bM0jwLyW~>JiEDb;gkY-?903uZ&B2$1&HYNx^ zDA>`Efd!<Bk>RtICX4^d>kcP3D_Tu{TGO(n=!7447oXIVzVwwpzTKM}UvC?<M)^tC zQ3=M6ccyafn;^T`S8Ja2s@oz`;>YVgA5Fa*_mtIp;`!v=Hd#CDi&k5$e`(8q_>f6= zng99?8xIuQJ{5^vx7sa&xn=*{k|U2AQ?{Rd<o;vgF7{5Dx$Rn)b~ADS-L!URrG@6* zbqS1#c>S%T5SCh0oSC0z3khRA104nD(wvggqEy?w)Y6io#2g)kfYPL#%w+G>%8>l* z)I8h7lq5@YOJfTo%aoMFR5JshZD7FI$w+=2>m?=?18qw#0lKA_^ayuE(v66Hd3!!+ z>}%l-1wCZ#!#6495g!OtO&6_{q>G}|oJ3HfNX#KURRqEHBGQD`Dotpb(7+!Y2+hMc zMdT101XN8A?L?=CijvekQ0Y{xmz<cKk(rlHN-=@Z><rb6oXrs_;<CS=Ii!xzL<$c4 zy5UIzUiiTZ!J%GX<PseQXlVjiD^pN+R1;fuAmRm<Byv-8^NT78lnw}Ou*BdC)`mz1 zr=+`~$v_!z5J2<|-vp3Ogzu>u^^?gd4)XKTGSf?oz*Pg8g#wBZ*o%fA7v-U80=dD7 zl$LOsF?`cUG4}L<GbE^*R)}v(AR;LxwIDSwB{eU(GBYnL72Luky0CD8>-B``MU)t8 zrl~+v2Xa#aVKElX!#7zVHzn`}0aeq(d@{-oEa`!~5(G>9qVyyXDJAVP12m<WV$D5- zOd7t)rjo4iplTY*CB5m9lb@arY>g6KjQBv*BNBws(kajcA%islpc;m6yeHuEHdUjS zoZbdBe#t8qV3CczN%7`k0W{{Z_cpNU9=^!{dv60<7*I7$kdfu{3y2w-LezZz5cP;G zU!SUsH_Jmc4BxoMp5>w5rfT#SlU|}16lLaSmSmOz$D;9%HX~v<0ImfQ$rmNQnL)-9 z<gpfT2tC6$o|6gqoT^c6i#MtZ@^f-3^)2+w^h{y12WG&@g8+~)BGO`CP3+YIPZF?# zWxxaIuz?*AO~GV<D2L=@Pygv^R#tm2_QmnIZ}7|EyXUg(sBFMi*DK8DUf1k4y4bsW z&t2J$6}8pH3fV&04^1{C%rRSKvsw1ilR8VI$pRXjJFeGD>Kk?>b~!x@Jm|Nd^~cxk zSu3TT#hf;oOn=+7EjGDQ=GJ0ar=~^MPOm=vkKJ|KwoD$!n$_PHn#=+F<;3FuhkyJ~ zJ;Ue$_8%ewEP(SWM2*`dTO=B%r6n7uS)^GQnI<R0LWj|vaQN$i=WtLW0cbBY5>TgW V5HZ08i6fBo@Qn^%geR#OCjdT)N+19L literal 0 HcmV?d00001 diff --git a/Jellyfin.Data/obj/Jellyfin.Data.csproj.nuget.dgspec.json b/Jellyfin.Data/obj/Jellyfin.Data.csproj.nuget.dgspec.json new file mode 100644 index 00000000..54b17e29 --- /dev/null +++ b/Jellyfin.Data/obj/Jellyfin.Data.csproj.nuget.dgspec.json @@ -0,0 +1,1161 @@ +{ + "format": 1, + "restore": { + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": {} + }, + "projects": { + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "projectName": "Jellyfin.Data", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Logging": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "projectName": "Jellyfin.CodeAnalysis", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "netstandard2.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "netstandard2.0": { + "targetAlias": "netstandard2.0", + "projectReferences": {} + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "netstandard2.0": { + "targetAlias": "netstandard2.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[3.11.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.CSharp": { + "suppressParent": "All", + "target": "Package", + "version": "[5.0.0, )", + "versionCentrallyManaged": true + }, + "NETStandard.Library": { + "suppressParent": "All", + "target": "Package", + "version": "[2.0.3, )", + "autoReferenced": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/RuntimeIdentifierGraph.json" + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "version": "10.11.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "projectName": "Jellyfin.Database.Implementations", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Design": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Relational": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Tools": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Polly": { + "target": "Package", + "version": "[8.6.5, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + } + } +} \ No newline at end of file diff --git a/Jellyfin.Data/obj/Jellyfin.Data.csproj.nuget.g.props b/Jellyfin.Data/obj/Jellyfin.Data.csproj.nuget.g.props new file mode 100644 index 00000000..aa204e43 --- /dev/null +++ b/Jellyfin.Data/obj/Jellyfin.Data.csproj.nuget.g.props @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess> + <RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool> + <ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile> + <NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageRoot> + <NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageFolders> + <NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle> + <NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion> + </PropertyGroup> + <ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <SourceRoot Include="/home/wjones/.nuget/packages/" /> + </ItemGroup> + <ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props')" /> + <Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props')" /> + </ImportGroup> + <PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <PkgStyleCop_Analyzers_Unstable Condition=" '$(PkgStyleCop_Analyzers_Unstable)' == '' ">/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556</PkgStyleCop_Analyzers_Unstable> + <PkgSmartAnalyzers_MultithreadingAnalyzer Condition=" '$(PkgSmartAnalyzers_MultithreadingAnalyzer)' == '' ">/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31</PkgSmartAnalyzers_MultithreadingAnalyzer> + <PkgSerilogAnalyzer Condition=" '$(PkgSerilogAnalyzer)' == '' ">/home/wjones/.nuget/packages/seriloganalyzer/0.15.0</PkgSerilogAnalyzer> + <PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers Condition=" '$(PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers)' == '' ">/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0</PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers> + <PkgIDisposableAnalyzers Condition=" '$(PkgIDisposableAnalyzers)' == '' ">/home/wjones/.nuget/packages/idisposableanalyzers/4.0.8</PkgIDisposableAnalyzers> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/Jellyfin.Data/obj/Jellyfin.Data.csproj.nuget.g.targets b/Jellyfin.Data/obj/Jellyfin.Data.csproj.nuget.g.targets new file mode 100644 index 00000000..dda66fb2 --- /dev/null +++ b/Jellyfin.Data/obj/Jellyfin.Data.csproj.nuget.g.targets @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <Import Project="$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets')" /> + </ImportGroup> +</Project> \ No newline at end of file diff --git a/Jellyfin.Data/obj/project.assets.json b/Jellyfin.Data/obj/project.assets.json new file mode 100644 index 00000000..d34203b1 --- /dev/null +++ b/Jellyfin.Data/obj/project.assets.json @@ -0,0 +1,1406 @@ +{ + "version": 3, + "targets": { + "net10.0": { + "IDisposableAnalyzers/4.0.8": { + "type": "package" + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers/4.14.0": { + "type": "package", + "build": { + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props": {}, + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets": {} + } + }, + "Microsoft.EntityFrameworkCore/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "10.0.3", + "Microsoft.EntityFrameworkCore.Analyzers": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props": {} + } + }, + "Microsoft.EntityFrameworkCore.Abstractions/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + } + }, + "Microsoft.EntityFrameworkCore.Analyzers/10.0.3": { + "type": "package" + }, + "Microsoft.EntityFrameworkCore.Relational/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + } + }, + "Microsoft.Extensions.Caching.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Caching.Memory/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Caching.Memory.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Caching.Memory.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Configuration.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.DependencyInjection/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Logging/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Logging.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Logging.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Logging.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets": {} + } + }, + "Microsoft.Extensions.Options/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Options.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Options.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/Microsoft.Extensions.Options.targets": {} + } + }, + "Microsoft.Extensions.Primitives/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.Extensions.Primitives.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Primitives.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Polly/8.6.5": { + "type": "package", + "dependencies": { + "Polly.Core": "8.6.5" + }, + "compile": { + "lib/net6.0/Polly.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net6.0/Polly.dll": { + "related": ".pdb;.xml" + } + } + }, + "Polly.Core/8.6.5": { + "type": "package", + "compile": { + "lib/net8.0/Polly.Core.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net8.0/Polly.Core.dll": { + "related": ".pdb;.xml" + } + } + }, + "SerilogAnalyzer/0.15.0": { + "type": "package" + }, + "SmartAnalyzers.MultithreadingAnalyzer/1.1.31": { + "type": "package" + }, + "StyleCop.Analyzers/1.2.0-beta.556": { + "type": "package", + "dependencies": { + "StyleCop.Analyzers.Unstable": "1.2.0.556" + } + }, + "StyleCop.Analyzers.Unstable/1.2.0.556": { + "type": "package" + }, + "Jellyfin.CodeAnalysis/1.0.0": { + "type": "project", + "framework": ".NETStandard,Version=v2.0", + "compile": { + "bin/placeholder/Jellyfin.CodeAnalysis.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.CodeAnalysis.dll": {} + } + }, + "Jellyfin.Database.Implementations/10.11.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Polly": "8.6.5" + }, + "compile": { + "bin/placeholder/Jellyfin.Database.Implementations.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Database.Implementations.dll": {} + } + } + } + }, + "libraries": { + "IDisposableAnalyzers/4.0.8": { + "sha512": "vNi4NMG0CcJyjXxiNDcQ21FwV/whM9o9OEZKD+oP7tuxAqFEzX/x5OhC3OZJqW/w+8GOtCmJPBquYgMWgz0rfQ==", + "type": "package", + "path": "idisposableanalyzers/4.0.8", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/IDisposableAnalyzers.dll", + "idisposableanalyzers.4.0.8.nupkg.sha512", + "idisposableanalyzers.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers/4.14.0": { + "sha512": "gSWJlDWwmDhtbrEJGiHqvEjz9KthIiFD0qYB8zZ6a7z+xpMSPEtM9yTYELSa58iFWYlzSRqP9FXO6KoT3+ZMtg==", + "type": "package", + "path": "microsoft.codeanalysis.bannedapianalyzers/4.14.0", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.txt", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.dll", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.CSharp.BannedApiAnalyzers.dll", + "analyzers/dotnet/cs/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/de/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/es/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/fr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/it/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ja/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ko/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/pl/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/pt-BR/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ru/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/tr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hans/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hant/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.BannedApiAnalyzers.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.VisualBasic.BannedApiAnalyzers.dll", + "analyzers/dotnet/vb/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/de/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/es/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/fr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/it/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ja/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ko/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/pl/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/pt-BR/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ru/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/tr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hans/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hant/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props", + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets", + "documentation/Microsoft.CodeAnalysis.BannedApiAnalyzers.md", + "documentation/Microsoft.CodeAnalysis.BannedApiAnalyzers.sarif", + "editorconfig/AllRulesDefault/.editorconfig", + "editorconfig/AllRulesDisabled/.editorconfig", + "editorconfig/AllRulesEnabled/.editorconfig", + "editorconfig/ApiDesignRulesDefault/.editorconfig", + "editorconfig/ApiDesignRulesEnabled/.editorconfig", + "editorconfig/DataflowRulesDefault/.editorconfig", + "editorconfig/DataflowRulesEnabled/.editorconfig", + "editorconfig/PortedFromFxCopRulesDefault/.editorconfig", + "editorconfig/PortedFromFxCopRulesEnabled/.editorconfig", + "microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512", + "microsoft.codeanalysis.bannedapianalyzers.nuspec", + "rulesets/AllRulesDefault.ruleset", + "rulesets/AllRulesDisabled.ruleset", + "rulesets/AllRulesEnabled.ruleset", + "rulesets/ApiDesignRulesDefault.ruleset", + "rulesets/ApiDesignRulesEnabled.ruleset", + "rulesets/DataflowRulesDefault.ruleset", + "rulesets/DataflowRulesEnabled.ruleset", + "rulesets/PortedFromFxCopRulesDefault.ruleset", + "rulesets/PortedFromFxCopRulesEnabled.ruleset", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Microsoft.EntityFrameworkCore/10.0.3": { + "sha512": "CDEImwD4A7BseABJMCpLZnhfFjmPY/bHwhhS70elc6gLI/bYUEOhxWt7PmaNGYGhIEzOnStlCy5QcVb+8dod5Q==", + "type": "package", + "path": "microsoft.entityframeworkcore/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props", + "lib/net10.0/Microsoft.EntityFrameworkCore.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.xml", + "microsoft.entityframeworkcore.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Abstractions/10.0.3": { + "sha512": "c7Uoz381xnMHNBRB8eHRhGgzUtXbgddlbODhwZRrTSzZWDharp3RkJsFwhxyESbeXhCqmML7VdvjMQ7uu+HreA==", + "type": "package", + "path": "microsoft.entityframeworkcore.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.xml", + "microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.abstractions.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Analyzers/10.0.3": { + "sha512": "lVABgJTyTUNE7Bi0bSu4dWHiCHIXEGzTh/kLh0N07IgU/tIKwTeBPp8tgV4x2iKj4h7iPLo8oXzyHmLDGtAE1g==", + "type": "package", + "path": "microsoft.entityframeworkcore.analyzers/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "analyzers/dotnet/cs/Microsoft.EntityFrameworkCore.Analyzers.dll", + "docs/PACKAGE.md", + "microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.analyzers.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Relational/10.0.3": { + "sha512": "Pmh60OK9neVr/M0FJwm9hlzm2bD4Kd65SID8E6SP5c90tExNgXwORrlEWl0oGU/ig9ifpNN4PSpIrnHNozlT5w==", + "type": "package", + "path": "microsoft.entityframeworkcore.relational/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.xml", + "microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.relational.nuspec" + ] + }, + "Microsoft.Extensions.Caching.Abstractions/10.0.3": { + "sha512": "5dtXBvI8t3z8pF4tB38JYgi/enCL/DwSXxpqShgFz3SHJ7IzqFIMs6Gu5ik8sNZzcO9qQs3xIDpB3vDamkYG+Q==", + "type": "package", + "path": "microsoft.extensions.caching.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Caching.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Caching.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.xml", + "microsoft.extensions.caching.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.caching.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Caching.Memory/10.0.3": { + "sha512": "No4fVh0z30SWqiWFRoA4PNdrEco6OjXvCqRFvlmRgDQqqks2bRDdeavUgWEiAX153ZAwW9loUgbxcvuP4NKQLg==", + "type": "package", + "path": "microsoft.extensions.caching.memory/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Caching.Memory.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Caching.Memory.targets", + "lib/net10.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net10.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/net462/Microsoft.Extensions.Caching.Memory.dll", + "lib/net462/Microsoft.Extensions.Caching.Memory.xml", + "lib/net8.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net8.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/net9.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net9.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.xml", + "microsoft.extensions.caching.memory.10.0.3.nupkg.sha512", + "microsoft.extensions.caching.memory.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Configuration.Abstractions/10.0.3": { + "sha512": "xVDHL0+SIgemfh95fTO9cGLe17TWv/ZP0n7m01z8X6pzt2DmQpucioWR/mYZA1sRlkWnkXzfl0JweLNWmE9WMg==", + "type": "package", + "path": "microsoft.extensions.configuration.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Configuration.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Configuration.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "microsoft.extensions.configuration.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.configuration.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyInjection/10.0.3": { + "sha512": "2DLOmC0EkB2smVK8lPP1PIKEgL1arE3CMp9XSIQB/Y7ev5nnnyuM/PizKJ6QfLD08QCYoopSC9SFdbYglDomYg==", + "type": "package", + "path": "microsoft.extensions.dependencyinjection/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyInjection.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.targets", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/net462/Microsoft.Extensions.DependencyInjection.dll", + "lib/net462/Microsoft.Extensions.DependencyInjection.xml", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.dll", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.xml", + "microsoft.extensions.dependencyinjection.10.0.3.nupkg.sha512", + "microsoft.extensions.dependencyinjection.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/10.0.3": { + "sha512": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==", + "type": "package", + "path": "microsoft.extensions.dependencyinjection.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyInjection.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "microsoft.extensions.dependencyinjection.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.dependencyinjection.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Logging/10.0.3": { + "sha512": "8D9Er1cGXNjNDIB+VLBNHn386L5ls2FoiG9a6o12gyn+GG3w6jdfUhzT8dtBnKcevE7/fsVA8MS3FBgFfClFtQ==", + "type": "package", + "path": "microsoft.extensions.logging/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Logging.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Logging.targets", + "lib/net10.0/Microsoft.Extensions.Logging.dll", + "lib/net10.0/Microsoft.Extensions.Logging.xml", + "lib/net462/Microsoft.Extensions.Logging.dll", + "lib/net462/Microsoft.Extensions.Logging.xml", + "lib/net8.0/Microsoft.Extensions.Logging.dll", + "lib/net8.0/Microsoft.Extensions.Logging.xml", + "lib/net9.0/Microsoft.Extensions.Logging.dll", + "lib/net9.0/Microsoft.Extensions.Logging.xml", + "lib/netstandard2.0/Microsoft.Extensions.Logging.dll", + "lib/netstandard2.0/Microsoft.Extensions.Logging.xml", + "lib/netstandard2.1/Microsoft.Extensions.Logging.dll", + "lib/netstandard2.1/Microsoft.Extensions.Logging.xml", + "microsoft.extensions.logging.10.0.3.nupkg.sha512", + "microsoft.extensions.logging.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Logging.Abstractions/10.0.3": { + "sha512": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", + "type": "package", + "path": "microsoft.extensions.logging.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn3.11/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn3.11/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn4.0/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "buildTransitive/net461/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/net462/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.xml", + "microsoft.extensions.logging.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.logging.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Options/10.0.3": { + "sha512": "hU6WzGTPvPoLA2ng1ILvWQb3g0qORdlHNsxI8IcPLumJb3suimYUl+bbDzdo1V4KFsvVhnMWzysHpKbZaoDQPQ==", + "type": "package", + "path": "microsoft.extensions.options/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Options.SourceGeneration.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "buildTransitive/net461/Microsoft.Extensions.Options.targets", + "buildTransitive/net462/Microsoft.Extensions.Options.targets", + "buildTransitive/net8.0/Microsoft.Extensions.Options.targets", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Options.targets", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Options.targets", + "lib/net10.0/Microsoft.Extensions.Options.dll", + "lib/net10.0/Microsoft.Extensions.Options.xml", + "lib/net462/Microsoft.Extensions.Options.dll", + "lib/net462/Microsoft.Extensions.Options.xml", + "lib/net8.0/Microsoft.Extensions.Options.dll", + "lib/net8.0/Microsoft.Extensions.Options.xml", + "lib/net9.0/Microsoft.Extensions.Options.dll", + "lib/net9.0/Microsoft.Extensions.Options.xml", + "lib/netstandard2.0/Microsoft.Extensions.Options.dll", + "lib/netstandard2.0/Microsoft.Extensions.Options.xml", + "lib/netstandard2.1/Microsoft.Extensions.Options.dll", + "lib/netstandard2.1/Microsoft.Extensions.Options.xml", + "microsoft.extensions.options.10.0.3.nupkg.sha512", + "microsoft.extensions.options.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Primitives/10.0.3": { + "sha512": "GEcpTwo7sUoLGGNTqV1FZEuL+tTD9m81NX/mh099dqGNna07/UGZShKQNZRw4hv6nlliSUwYQgSYc7OR99Jufg==", + "type": "package", + "path": "microsoft.extensions.primitives/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Primitives.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Primitives.targets", + "lib/net10.0/Microsoft.Extensions.Primitives.dll", + "lib/net10.0/Microsoft.Extensions.Primitives.xml", + "lib/net462/Microsoft.Extensions.Primitives.dll", + "lib/net462/Microsoft.Extensions.Primitives.xml", + "lib/net8.0/Microsoft.Extensions.Primitives.dll", + "lib/net8.0/Microsoft.Extensions.Primitives.xml", + "lib/net9.0/Microsoft.Extensions.Primitives.dll", + "lib/net9.0/Microsoft.Extensions.Primitives.xml", + "lib/netstandard2.0/Microsoft.Extensions.Primitives.dll", + "lib/netstandard2.0/Microsoft.Extensions.Primitives.xml", + "microsoft.extensions.primitives.10.0.3.nupkg.sha512", + "microsoft.extensions.primitives.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Polly/8.6.5": { + "sha512": "VqtW2ZE/ALvQMAH1cQY3qZ2cF2OXa3oe/HKMdOv6Q02HCoEW0rsFNfcBONXlHBe1TnjWW1vdRxBEkPeq0/2FHA==", + "type": "package", + "path": "polly/8.6.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "lib/net462/Polly.dll", + "lib/net462/Polly.pdb", + "lib/net462/Polly.xml", + "lib/net472/Polly.dll", + "lib/net472/Polly.pdb", + "lib/net472/Polly.xml", + "lib/net6.0/Polly.dll", + "lib/net6.0/Polly.pdb", + "lib/net6.0/Polly.xml", + "lib/netstandard2.0/Polly.dll", + "lib/netstandard2.0/Polly.pdb", + "lib/netstandard2.0/Polly.xml", + "package-icon.png", + "package-readme.md", + "polly.8.6.5.nupkg.sha512", + "polly.nuspec" + ] + }, + "Polly.Core/8.6.5": { + "sha512": "t+sUVrIwvo7UmsgHGgOG9F0GDZSRIm47u2ylH17Gvcv1q5hNEwgD5GoBlFyc0kh/pebmPyrAgvGsR/65ZBaXlg==", + "type": "package", + "path": "polly.core/8.6.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "lib/net462/Polly.Core.dll", + "lib/net462/Polly.Core.pdb", + "lib/net462/Polly.Core.xml", + "lib/net472/Polly.Core.dll", + "lib/net472/Polly.Core.pdb", + "lib/net472/Polly.Core.xml", + "lib/net6.0/Polly.Core.dll", + "lib/net6.0/Polly.Core.pdb", + "lib/net6.0/Polly.Core.xml", + "lib/net8.0/Polly.Core.dll", + "lib/net8.0/Polly.Core.pdb", + "lib/net8.0/Polly.Core.xml", + "lib/netstandard2.0/Polly.Core.dll", + "lib/netstandard2.0/Polly.Core.pdb", + "lib/netstandard2.0/Polly.Core.xml", + "package-icon.png", + "package-readme.md", + "polly.core.8.6.5.nupkg.sha512", + "polly.core.nuspec" + ] + }, + "SerilogAnalyzer/0.15.0": { + "sha512": "sVpwfls4MfNnwIXLSGCgaUnV+c9kgJ8ia6GsyRcpd4Vs3gLogSDtSYBYrre2K2u/PNMo8GgG09RehwVnze70Tw==", + "type": "package", + "path": "seriloganalyzer/0.15.0", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/SerilogAnalyzer.dll", + "seriloganalyzer.0.15.0.nupkg.sha512", + "seriloganalyzer.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "SmartAnalyzers.MultithreadingAnalyzer/1.1.31": { + "sha512": "2f2k7bbhDd132ArglCKpzKoWBcp3uzbIFcb4aosnlqIKlfYKDE2HevBVRNVa+LkWFnjXFFWs47Bo96fu8iS//Q==", + "type": "package", + "path": "smartanalyzers.multithreadinganalyzer/1.1.31", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/SmartAnalyzers.MultithreadingAnalyzer.dll", + "smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512", + "smartanalyzers.multithreadinganalyzer.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "StyleCop.Analyzers/1.2.0-beta.556": { + "sha512": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", + "type": "package", + "path": "stylecop.analyzers/1.2.0-beta.556", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "THIRD-PARTY-NOTICES.txt", + "stylecop.analyzers.1.2.0-beta.556.nupkg.sha512", + "stylecop.analyzers.nuspec" + ] + }, + "StyleCop.Analyzers.Unstable/1.2.0.556": { + "sha512": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==", + "type": "package", + "path": "stylecop.analyzers.unstable/1.2.0.556", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "THIRD-PARTY-NOTICES.txt", + "analyzers/dotnet/cs/StyleCop.Analyzers.CodeFixes.dll", + "analyzers/dotnet/cs/StyleCop.Analyzers.dll", + "analyzers/dotnet/cs/de-DE/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/en-GB/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/es-MX/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/fr-FR/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/pl-PL/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/pt-BR/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/ru-RU/StyleCop.Analyzers.resources.dll", + "rulesets/StyleCopAnalyzersDefault.ruleset", + "stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512", + "stylecop.analyzers.unstable.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Jellyfin.CodeAnalysis/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "msbuildProject": "../src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "Jellyfin.Database.Implementations/10.11.0": { + "type": "project", + "path": "../src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "msbuildProject": "../src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + } + }, + "projectFileDependencyGroups": { + "net10.0": [ + "IDisposableAnalyzers >= 4.0.8", + "Jellyfin.CodeAnalysis >= 1.0.0", + "Jellyfin.Database.Implementations >= 10.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers >= 4.14.0", + "Microsoft.Extensions.Logging >= 10.0.3", + "SerilogAnalyzer >= 0.15.0", + "SmartAnalyzers.MultithreadingAnalyzer >= 1.1.31", + "StyleCop.Analyzers >= 1.2.0-beta.556" + ] + }, + "packageFolders": { + "/home/wjones/.nuget/packages/": {} + }, + "project": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "projectName": "Jellyfin.Data", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Logging": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + } +} \ No newline at end of file diff --git a/Jellyfin.Data/obj/project.nuget.cache b/Jellyfin.Data/obj/project.nuget.cache new file mode 100644 index 00000000..26d01ee2 --- /dev/null +++ b/Jellyfin.Data/obj/project.nuget.cache @@ -0,0 +1,30 @@ +{ + "version": 2, + "dgSpecHash": "EHhu2bd8aNc=", + "success": true, + "projectFilePath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "expectedPackageFiles": [ + "/home/wjones/.nuget/packages/idisposableanalyzers/4.0.8/idisposableanalyzers.4.0.8.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0/microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore/10.0.3/microsoft.entityframeworkcore.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.abstractions/10.0.3/microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.analyzers/10.0.3/microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.relational/10.0.3/microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.caching.abstractions/10.0.3/microsoft.extensions.caching.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.caching.memory/10.0.3/microsoft.extensions.caching.memory.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.configuration.abstractions/10.0.3/microsoft.extensions.configuration.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection/10.0.3/microsoft.extensions.dependencyinjection.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/10.0.3/microsoft.extensions.dependencyinjection.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.logging/10.0.3/microsoft.extensions.logging.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.logging.abstractions/10.0.3/microsoft.extensions.logging.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.options/10.0.3/microsoft.extensions.options.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.primitives/10.0.3/microsoft.extensions.primitives.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/polly/8.6.5/polly.8.6.5.nupkg.sha512", + "/home/wjones/.nuget/packages/polly.core/8.6.5/polly.core.8.6.5.nupkg.sha512", + "/home/wjones/.nuget/packages/seriloganalyzer/0.15.0/seriloganalyzer.0.15.0.nupkg.sha512", + "/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31/smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512", + "/home/wjones/.nuget/packages/stylecop.analyzers/1.2.0-beta.556/stylecop.analyzers.1.2.0-beta.556.nupkg.sha512", + "/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556/stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Jellyfin.Data/obj/project.packagespec.json b/Jellyfin.Data/obj/project.packagespec.json new file mode 100644 index 00000000..93b46ddc --- /dev/null +++ b/Jellyfin.Data/obj/project.packagespec.json @@ -0,0 +1 @@ +"restore":{"projectUniqueName":"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj","projectName":"Jellyfin.Data","projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj","outputPath":"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/obj/","projectStyle":"PackageReference","centralPackageVersionsManagementEnabled":true,"originalTargetFrameworks":["net10.0"],"sources":{"/usr/lib/dotnet/library-packs":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net10.0":{"targetAlias":"net10.0","projectReferences":{"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj"}}}},"warningProperties":{"allWarningsAsErrors":true,"warnAsError":["NU1605"],"warnNotAsError":["NU1902","NU1903"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"all"},"SdkAnalysisLevel":"10.0.100"}"frameworks":{"net10.0":{"targetAlias":"net10.0","dependencies":{"IDisposableAnalyzers":{"include":"Runtime, Build, Native, ContentFiles, Analyzers","suppressParent":"All","target":"Package","version":"[4.0.8, )","versionCentrallyManaged":true},"Microsoft.CodeAnalysis.BannedApiAnalyzers":{"include":"Runtime, Build, Native, ContentFiles, Analyzers","suppressParent":"All","target":"Package","version":"[4.14.0, )","versionCentrallyManaged":true},"Microsoft.Extensions.Logging":{"target":"Package","version":"[10.0.3, )","versionCentrallyManaged":true},"SerilogAnalyzer":{"suppressParent":"All","target":"Package","version":"[0.15.0, )","versionCentrallyManaged":true},"SmartAnalyzers.MultithreadingAnalyzer":{"suppressParent":"All","target":"Package","version":"[1.1.31, )","versionCentrallyManaged":true},"StyleCop.Analyzers":{"suppressParent":"All","target":"Package","version":"[1.2.0-beta.556, )","versionCentrallyManaged":true}},"centralPackageVersions":{"AsyncKeyedLock":"8.0.2","AutoFixture":"4.18.1","AutoFixture.AutoMoq":"4.18.1","AutoFixture.Xunit2":"4.18.1","BDInfo":"0.8.0","BitFaster.Caching":"2.5.4","BlurHashSharp":"1.4.0-pre.1","BlurHashSharp.SkiaSharp":"1.4.0-pre.1","CommandLineParser":"2.9.1","coverlet.collector":"8.0.0","Diacritics":"4.1.4","DiscUtils.Udf":"0.16.13","DotNet.Glob":"3.1.3","FsCheck.Xunit":"3.3.2","HarfBuzzSharp.NativeAssets.Linux":"8.3.1.1","ICU4N.Transliterator":"60.1.0-alpha.356","IDisposableAnalyzers":"4.0.8","Ignore":"0.2.1","Jellyfin.XmlTv":"10.8.0","libse":"4.0.12","LrcParser":"2025.623.0","MetaBrainz.MusicBrainz":"8.0.1","Microsoft.AspNetCore.Authorization":"10.0.3","Microsoft.AspNetCore.Mvc.Testing":"10.0.3","Microsoft.CodeAnalysis.Analyzers":"3.11.0","Microsoft.CodeAnalysis.BannedApiAnalyzers":"4.14.0","Microsoft.CodeAnalysis.Common":"5.0.0","Microsoft.CodeAnalysis.CSharp":"5.0.0","Microsoft.Data.Sqlite":"10.0.3","Microsoft.EntityFrameworkCore.Design":"10.0.3","Microsoft.EntityFrameworkCore.Relational":"10.0.3","Microsoft.EntityFrameworkCore.Sqlite":"10.0.3","Microsoft.EntityFrameworkCore.Tools":"10.0.3","Microsoft.Extensions.Caching.Abstractions":"10.0.3","Microsoft.Extensions.Caching.Memory":"10.0.3","Microsoft.Extensions.Configuration.Abstractions":"10.0.3","Microsoft.Extensions.Configuration.Binder":"10.0.3","Microsoft.Extensions.DependencyInjection":"10.0.3","Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore":"10.0.3","Microsoft.Extensions.Hosting.Abstractions":"10.0.3","Microsoft.Extensions.Http":"10.0.3","Microsoft.Extensions.Logging":"10.0.3","Microsoft.Extensions.Options":"10.0.3","Microsoft.NET.Test.Sdk":"18.0.1","MimeTypes":"2.5.2","Moq":"4.18.4","Morestachio":"5.0.1.631","NEbml":"1.1.0.5","Newtonsoft.Json":"13.0.4","PlaylistsNET":"1.4.1","Polly":"8.6.5","prometheus-net":"8.2.1","prometheus-net.AspNetCore":"8.2.1","prometheus-net.DotNetRuntime":"4.4.1","Serilog.AspNetCore":"10.0.0","Serilog.Enrichers.Thread":"4.0.0","Serilog.Expressions":"5.0.0","Serilog.Settings.Configuration":"10.0.0","Serilog.Sinks.Async":"2.1.0","Serilog.Sinks.Console":"6.1.1","Serilog.Sinks.File":"7.0.0","Serilog.Sinks.Graylog":"3.1.1","SerilogAnalyzer":"0.15.0","SharpFuzz":"2.2.0","SkiaSharp":"[3.116.1]","SkiaSharp.HarfBuzz":"[3.116.1]","SkiaSharp.NativeAssets.Linux":"[3.116.1]","SmartAnalyzers.MultithreadingAnalyzer":"1.1.31","StyleCop.Analyzers":"1.2.0-beta.556","Svg.Skia":"3.4.1","Swashbuckle.AspNetCore":"7.3.2","Swashbuckle.AspNetCore.ReDoc":"6.9.0","System.Text.Json":"10.0.3","TagLibSharp":"2.3.0","TMDbLib":"2.3.0","UTF.Unknown":"2.6.0","xunit":"2.9.3","Xunit.Priority":"1.1.6","xunit.runner.visualstudio":"2.8.2","Xunit.SkippableFact":"1.5.61","z440.atl.core":"7.11.0"},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json","packagesToPrune":{"Microsoft.CSharp":"(,4.7.32767]","Microsoft.VisualBasic":"(,10.4.32767]","Microsoft.Win32.Primitives":"(,4.3.32767]","Microsoft.Win32.Registry":"(,5.0.32767]","runtime.any.System.Collections":"(,4.3.32767]","runtime.any.System.Diagnostics.Tools":"(,4.3.32767]","runtime.any.System.Diagnostics.Tracing":"(,4.3.32767]","runtime.any.System.Globalization":"(,4.3.32767]","runtime.any.System.Globalization.Calendars":"(,4.3.32767]","runtime.any.System.IO":"(,4.3.32767]","runtime.any.System.Reflection":"(,4.3.32767]","runtime.any.System.Reflection.Extensions":"(,4.3.32767]","runtime.any.System.Reflection.Primitives":"(,4.3.32767]","runtime.any.System.Resources.ResourceManager":"(,4.3.32767]","runtime.any.System.Runtime":"(,4.3.32767]","runtime.any.System.Runtime.Handles":"(,4.3.32767]","runtime.any.System.Runtime.InteropServices":"(,4.3.32767]","runtime.any.System.Text.Encoding":"(,4.3.32767]","runtime.any.System.Text.Encoding.Extensions":"(,4.3.32767]","runtime.any.System.Threading.Tasks":"(,4.3.32767]","runtime.any.System.Threading.Timer":"(,4.3.32767]","runtime.aot.System.Collections":"(,4.3.32767]","runtime.aot.System.Diagnostics.Tools":"(,4.3.32767]","runtime.aot.System.Diagnostics.Tracing":"(,4.3.32767]","runtime.aot.System.Globalization":"(,4.3.32767]","runtime.aot.System.Globalization.Calendars":"(,4.3.32767]","runtime.aot.System.IO":"(,4.3.32767]","runtime.aot.System.Reflection":"(,4.3.32767]","runtime.aot.System.Reflection.Extensions":"(,4.3.32767]","runtime.aot.System.Reflection.Primitives":"(,4.3.32767]","runtime.aot.System.Resources.ResourceManager":"(,4.3.32767]","runtime.aot.System.Runtime":"(,4.3.32767]","runtime.aot.System.Runtime.Handles":"(,4.3.32767]","runtime.aot.System.Runtime.InteropServices":"(,4.3.32767]","runtime.aot.System.Text.Encoding":"(,4.3.32767]","runtime.aot.System.Text.Encoding.Extensions":"(,4.3.32767]","runtime.aot.System.Threading.Tasks":"(,4.3.32767]","runtime.aot.System.Threading.Timer":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.unix.Microsoft.Win32.Primitives":"(,4.3.32767]","runtime.unix.System.Console":"(,4.3.32767]","runtime.unix.System.Diagnostics.Debug":"(,4.3.32767]","runtime.unix.System.IO.FileSystem":"(,4.3.32767]","runtime.unix.System.Net.Primitives":"(,4.3.32767]","runtime.unix.System.Net.Sockets":"(,4.3.32767]","runtime.unix.System.Private.Uri":"(,4.3.32767]","runtime.unix.System.Runtime.Extensions":"(,4.3.32767]","runtime.win.Microsoft.Win32.Primitives":"(,4.3.32767]","runtime.win.System.Console":"(,4.3.32767]","runtime.win.System.Diagnostics.Debug":"(,4.3.32767]","runtime.win.System.IO.FileSystem":"(,4.3.32767]","runtime.win.System.Net.Primitives":"(,4.3.32767]","runtime.win.System.Net.Sockets":"(,4.3.32767]","runtime.win.System.Runtime.Extensions":"(,4.3.32767]","runtime.win10-arm-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win10-arm64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win10-x64-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win10-x86-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win7-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win7-x86.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win7.System.Private.Uri":"(,4.3.32767]","runtime.win8-arm.runtime.native.System.IO.Compression":"(,4.3.32767]","System.AppContext":"(,4.3.32767]","System.Buffers":"(,5.0.32767]","System.Collections":"(,4.3.32767]","System.Collections.Concurrent":"(,4.3.32767]","System.Collections.Immutable":"(,10.0.32767]","System.Collections.NonGeneric":"(,4.3.32767]","System.Collections.Specialized":"(,4.3.32767]","System.ComponentModel":"(,4.3.32767]","System.ComponentModel.Annotations":"(,4.3.32767]","System.ComponentModel.EventBasedAsync":"(,4.3.32767]","System.ComponentModel.Primitives":"(,4.3.32767]","System.ComponentModel.TypeConverter":"(,4.3.32767]","System.Console":"(,4.3.32767]","System.Data.Common":"(,4.3.32767]","System.Data.DataSetExtensions":"(,4.4.32767]","System.Diagnostics.Contracts":"(,4.3.32767]","System.Diagnostics.Debug":"(,4.3.32767]","System.Diagnostics.DiagnosticSource":"(,10.0.32767]","System.Diagnostics.FileVersionInfo":"(,4.3.32767]","System.Diagnostics.Process":"(,4.3.32767]","System.Diagnostics.StackTrace":"(,4.3.32767]","System.Diagnostics.TextWriterTraceListener":"(,4.3.32767]","System.Diagnostics.Tools":"(,4.3.32767]","System.Diagnostics.TraceSource":"(,4.3.32767]","System.Diagnostics.Tracing":"(,4.3.32767]","System.Drawing.Primitives":"(,4.3.32767]","System.Dynamic.Runtime":"(,4.3.32767]","System.Formats.Asn1":"(,10.0.32767]","System.Formats.Tar":"(,10.0.32767]","System.Globalization":"(,4.3.32767]","System.Globalization.Calendars":"(,4.3.32767]","System.Globalization.Extensions":"(,4.3.32767]","System.IO":"(,4.3.32767]","System.IO.Compression":"(,4.3.32767]","System.IO.Compression.ZipFile":"(,4.3.32767]","System.IO.FileSystem":"(,4.3.32767]","System.IO.FileSystem.AccessControl":"(,4.4.32767]","System.IO.FileSystem.DriveInfo":"(,4.3.32767]","System.IO.FileSystem.Primitives":"(,4.3.32767]","System.IO.FileSystem.Watcher":"(,4.3.32767]","System.IO.IsolatedStorage":"(,4.3.32767]","System.IO.MemoryMappedFiles":"(,4.3.32767]","System.IO.Pipelines":"(,10.0.32767]","System.IO.Pipes":"(,4.3.32767]","System.IO.Pipes.AccessControl":"(,5.0.32767]","System.IO.UnmanagedMemoryStream":"(,4.3.32767]","System.Linq":"(,4.3.32767]","System.Linq.AsyncEnumerable":"(,10.0.32767]","System.Linq.Expressions":"(,4.3.32767]","System.Linq.Parallel":"(,4.3.32767]","System.Linq.Queryable":"(,4.3.32767]","System.Memory":"(,5.0.32767]","System.Net.Http":"(,4.3.32767]","System.Net.Http.Json":"(,10.0.32767]","System.Net.NameResolution":"(,4.3.32767]","System.Net.NetworkInformation":"(,4.3.32767]","System.Net.Ping":"(,4.3.32767]","System.Net.Primitives":"(,4.3.32767]","System.Net.Requests":"(,4.3.32767]","System.Net.Security":"(,4.3.32767]","System.Net.ServerSentEvents":"(,10.0.32767]","System.Net.Sockets":"(,4.3.32767]","System.Net.WebHeaderCollection":"(,4.3.32767]","System.Net.WebSockets":"(,4.3.32767]","System.Net.WebSockets.Client":"(,4.3.32767]","System.Numerics.Vectors":"(,5.0.32767]","System.ObjectModel":"(,4.3.32767]","System.Private.DataContractSerialization":"(,4.3.32767]","System.Private.Uri":"(,4.3.32767]","System.Reflection":"(,4.3.32767]","System.Reflection.DispatchProxy":"(,6.0.32767]","System.Reflection.Emit":"(,4.7.32767]","System.Reflection.Emit.ILGeneration":"(,4.7.32767]","System.Reflection.Emit.Lightweight":"(,4.7.32767]","System.Reflection.Extensions":"(,4.3.32767]","System.Reflection.Metadata":"(,10.0.32767]","System.Reflection.Primitives":"(,4.3.32767]","System.Reflection.TypeExtensions":"(,4.3.32767]","System.Resources.Reader":"(,4.3.32767]","System.Resources.ResourceManager":"(,4.3.32767]","System.Resources.Writer":"(,4.3.32767]","System.Runtime":"(,4.3.32767]","System.Runtime.CompilerServices.Unsafe":"(,7.0.32767]","System.Runtime.CompilerServices.VisualC":"(,4.3.32767]","System.Runtime.Extensions":"(,4.3.32767]","System.Runtime.Handles":"(,4.3.32767]","System.Runtime.InteropServices":"(,4.3.32767]","System.Runtime.InteropServices.RuntimeInformation":"(,4.3.32767]","System.Runtime.Loader":"(,4.3.32767]","System.Runtime.Numerics":"(,4.3.32767]","System.Runtime.Serialization.Formatters":"(,4.3.32767]","System.Runtime.Serialization.Json":"(,4.3.32767]","System.Runtime.Serialization.Primitives":"(,4.3.32767]","System.Runtime.Serialization.Xml":"(,4.3.32767]","System.Security.AccessControl":"(,6.0.32767]","System.Security.Claims":"(,4.3.32767]","System.Security.Cryptography.Algorithms":"(,4.3.32767]","System.Security.Cryptography.Cng":"(,5.0.32767]","System.Security.Cryptography.Csp":"(,4.3.32767]","System.Security.Cryptography.Encoding":"(,4.3.32767]","System.Security.Cryptography.OpenSsl":"(,5.0.32767]","System.Security.Cryptography.Primitives":"(,4.3.32767]","System.Security.Cryptography.X509Certificates":"(,4.3.32767]","System.Security.Principal":"(,4.3.32767]","System.Security.Principal.Windows":"(,5.0.32767]","System.Security.SecureString":"(,4.3.32767]","System.Text.Encoding":"(,4.3.32767]","System.Text.Encoding.CodePages":"(,10.0.32767]","System.Text.Encoding.Extensions":"(,4.3.32767]","System.Text.Encodings.Web":"(,10.0.32767]","System.Text.Json":"(,10.0.32767]","System.Text.RegularExpressions":"(,4.3.32767]","System.Threading":"(,4.3.32767]","System.Threading.AccessControl":"(,10.0.32767]","System.Threading.Channels":"(,10.0.32767]","System.Threading.Overlapped":"(,4.3.32767]","System.Threading.Tasks":"(,4.3.32767]","System.Threading.Tasks.Dataflow":"(,10.0.32767]","System.Threading.Tasks.Extensions":"(,5.0.32767]","System.Threading.Tasks.Parallel":"(,4.3.32767]","System.Threading.Thread":"(,4.3.32767]","System.Threading.ThreadPool":"(,4.3.32767]","System.Threading.Timer":"(,4.3.32767]","System.ValueTuple":"(,4.5.32767]","System.Xml.ReaderWriter":"(,4.3.32767]","System.Xml.XDocument":"(,4.3.32767]","System.Xml.XmlDocument":"(,4.3.32767]","System.Xml.XmlSerializer":"(,4.3.32767]","System.Xml.XPath":"(,4.3.32767]","System.Xml.XPath.XDocument":"(,5.0.32767]"}}} \ No newline at end of file diff --git a/Jellyfin.Data/obj/rider.project.model.nuget.info b/Jellyfin.Data/obj/rider.project.model.nuget.info new file mode 100644 index 00000000..3b36e5e4 --- /dev/null +++ b/Jellyfin.Data/obj/rider.project.model.nuget.info @@ -0,0 +1 @@ +17714532046900000 \ No newline at end of file diff --git a/Jellyfin.Data/obj/rider.project.restore.info b/Jellyfin.Data/obj/rider.project.restore.info new file mode 100644 index 00000000..5d1e6d9e --- /dev/null +++ b/Jellyfin.Data/obj/rider.project.restore.info @@ -0,0 +1 @@ +17715044199300000 \ No newline at end of file diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs new file mode 100644 index 00000000..fe987b9d --- /dev/null +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using Jellyfin.Data.Events; +using Jellyfin.Data.Queries; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Querying; +using Microsoft.EntityFrameworkCore; + +namespace Jellyfin.Server.Implementations.Activity; + +/// <summary> +/// Manages the storage and retrieval of <see cref="ActivityLog"/> instances. +/// </summary> +public class ActivityManager : IActivityManager +{ + private readonly IDbContextFactory<JellyfinDbContext> _provider; + + /// <summary> + /// Initializes a new instance of the <see cref="ActivityManager"/> class. + /// </summary> + /// <param name="provider">The Jellyfin database provider.</param> + public ActivityManager(IDbContextFactory<JellyfinDbContext> provider) + { + _provider = provider; + } + + /// <inheritdoc/> + public event EventHandler<GenericEventArgs<ActivityLogEntry>>? EntryCreated; + + /// <inheritdoc/> + public async Task CreateAsync(ActivityLog entry) + { + var dbContext = await _provider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + dbContext.ActivityLogs.Add(entry); + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + + EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(ConvertToOldModel(entry))); + } + + /// <inheritdoc/> + public async Task<QueryResult<ActivityLogEntry>> GetPagedResultAsync(ActivityLogQuery query) + { + // TODO allow sorting and filtering by item id. Currently not possible because ActivityLog stores the item id as a string. + + var dbContext = await _provider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + // TODO switch to LeftJoin in .NET 10. + var entries = from a in dbContext.ActivityLogs + join u in dbContext.Users on a.UserId equals u.Id into ugj + from u in ugj.DefaultIfEmpty() + select new ExpandedActivityLog { ActivityLog = a, Username = u.Username }; + + if (query.HasUserId is not null) + { + entries = entries.Where(e => e.ActivityLog.UserId.Equals(default) != query.HasUserId.Value); + } + + if (query.MinDate is not null) + { + entries = entries.Where(e => e.ActivityLog.DateCreated >= query.MinDate.Value); + } + + if (query.MaxDate is not null) + { + entries = entries.Where(e => e.ActivityLog.DateCreated <= query.MaxDate.Value); + } + + if (!string.IsNullOrEmpty(query.Name)) + { + entries = entries.Where(e => EF.Functions.Like(e.ActivityLog.Name, $"%{query.Name}%")); + } + + if (!string.IsNullOrEmpty(query.Overview)) + { + entries = entries.Where(e => EF.Functions.Like(e.ActivityLog.Overview, $"%{query.Overview}%")); + } + + if (!string.IsNullOrEmpty(query.ShortOverview)) + { + entries = entries.Where(e => EF.Functions.Like(e.ActivityLog.ShortOverview, $"%{query.ShortOverview}%")); + } + + if (!string.IsNullOrEmpty(query.Type)) + { + entries = entries.Where(e => EF.Functions.Like(e.ActivityLog.Type, $"%{query.Type}%")); + } + + if (!query.ItemId.IsNullOrEmpty()) + { + var itemId = query.ItemId.Value.ToString("N"); + entries = entries.Where(e => e.ActivityLog.ItemId == itemId); + } + + if (!string.IsNullOrEmpty(query.Username)) + { + entries = entries.Where(e => EF.Functions.Like(e.Username, $"%{query.Username}%")); + } + + if (query.Severity is not null) + { + entries = entries.Where(e => e.ActivityLog.LogSeverity == query.Severity); + } + + return new QueryResult<ActivityLogEntry>( + query.Skip, + await entries.CountAsync().ConfigureAwait(false), + await ApplyOrdering(entries, query.OrderBy) + .Skip(query.Skip ?? 0) + .Take(query.Limit ?? 100) + .Select(entity => new ActivityLogEntry(entity.ActivityLog.Name, entity.ActivityLog.Type, entity.ActivityLog.UserId) + { + Id = entity.ActivityLog.Id, + Overview = entity.ActivityLog.Overview, + ShortOverview = entity.ActivityLog.ShortOverview, + ItemId = entity.ActivityLog.ItemId, + Date = entity.ActivityLog.DateCreated, + Severity = entity.ActivityLog.LogSeverity + }) + .ToListAsync() + .ConfigureAwait(false)); + } + } + + /// <inheritdoc /> + public async Task CleanAsync(DateTime startDate) + { + var dbContext = await _provider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + await dbContext.ActivityLogs + .Where(entry => entry.DateCreated <= startDate) + .ExecuteDeleteAsync() + .ConfigureAwait(false); + } + } + + private static ActivityLogEntry ConvertToOldModel(ActivityLog entry) + { + return new ActivityLogEntry(entry.Name, entry.Type, entry.UserId) + { + Id = entry.Id, + Overview = entry.Overview, + ShortOverview = entry.ShortOverview, + ItemId = entry.ItemId, + Date = entry.DateCreated, + Severity = entry.LogSeverity + }; + } + + private IOrderedQueryable<ExpandedActivityLog> ApplyOrdering(IQueryable<ExpandedActivityLog> query, IReadOnlyCollection<(ActivityLogSortBy, SortOrder)>? sorting) + { + if (sorting is null || sorting.Count == 0) + { + return query.OrderByDescending(e => e.ActivityLog.DateCreated); + } + + IOrderedQueryable<ExpandedActivityLog> ordered = null!; + + foreach (var (sortBy, sortOrder) in sorting) + { + var orderBy = MapOrderBy(sortBy); + + if (ordered == null) + { + ordered = sortOrder == SortOrder.Ascending + ? query.OrderBy(orderBy) + : query.OrderByDescending(orderBy); + } + else + { + ordered = sortOrder == SortOrder.Ascending + ? ordered.ThenBy(orderBy) + : ordered.ThenByDescending(orderBy); + } + } + + return ordered; + } + + private Expression<Func<ExpandedActivityLog, object?>> MapOrderBy(ActivityLogSortBy sortBy) + { + return sortBy switch + { + ActivityLogSortBy.Name => e => e.ActivityLog.Name, + ActivityLogSortBy.Overiew => e => e.ActivityLog.Overview, + ActivityLogSortBy.ShortOverview => e => e.ActivityLog.ShortOverview, + ActivityLogSortBy.Type => e => e.ActivityLog.Type, + ActivityLogSortBy.DateCreated => e => e.ActivityLog.DateCreated, + ActivityLogSortBy.Username => e => e.Username, + ActivityLogSortBy.LogSeverity => e => e.ActivityLog.LogSeverity, + _ => throw new ArgumentOutOfRangeException(nameof(sortBy), sortBy, "Unhandled ActivityLogSortBy") + }; + } + + private class ExpandedActivityLog + { + public ActivityLog ActivityLog { get; set; } = null!; + + public string? Username { get; set; } + } +} diff --git a/Jellyfin.Server.Implementations/DbConfiguration/DatabaseConfigurationFactory.cs b/Jellyfin.Server.Implementations/DbConfiguration/DatabaseConfigurationFactory.cs new file mode 100644 index 00000000..26d32f41 --- /dev/null +++ b/Jellyfin.Server.Implementations/DbConfiguration/DatabaseConfigurationFactory.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; + +namespace Jellyfin.Server.Implementations.DatabaseConfiguration; + +/// <summary> +/// Factory for constructing a database configuration. +/// </summary> +public class DatabaseConfigurationFactory : IConfigurationFactory +{ + /// <inheritdoc/> + public IEnumerable<ConfigurationStore> GetConfigurations() + { + yield return new DatabaseConfigurationStore(); + } +} diff --git a/Jellyfin.Server.Implementations/DbConfiguration/DatabaseConfigurationStore.cs b/Jellyfin.Server.Implementations/DbConfiguration/DatabaseConfigurationStore.cs new file mode 100644 index 00000000..53763056 --- /dev/null +++ b/Jellyfin.Server.Implementations/DbConfiguration/DatabaseConfigurationStore.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using Jellyfin.Database.Implementations.DbConfiguration; +using MediaBrowser.Common.Configuration; + +namespace Jellyfin.Server.Implementations.DatabaseConfiguration; + +/// <summary> +/// A configuration that stores database related settings. +/// </summary> +public class DatabaseConfigurationStore : ConfigurationStore +{ + /// <summary> + /// The name of the configuration in the storage. + /// </summary> + public const string StoreKey = "database"; + + /// <summary> + /// Initializes a new instance of the <see cref="DatabaseConfigurationStore"/> class. + /// </summary> + public DatabaseConfigurationStore() + { + ConfigurationType = typeof(DatabaseConfigurationOptions); + Key = StoreKey; + } +} diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs new file mode 100644 index 00000000..bcf348f8 --- /dev/null +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Data; +using Jellyfin.Data.Dtos; +using Jellyfin.Data.Events; +using Jellyfin.Data.Queries; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Entities.Security; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Querying; +using MediaBrowser.Model.Session; +using Microsoft.EntityFrameworkCore; + +namespace Jellyfin.Server.Implementations.Devices +{ + /// <summary> + /// Manages the creation, updating, and retrieval of devices. + /// </summary> + public class DeviceManager : IDeviceManager + { + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + private readonly IUserManager _userManager; + private readonly ConcurrentDictionary<string, ClientCapabilities> _capabilitiesMap = new(); + private readonly ConcurrentDictionary<int, Device> _devices; + private readonly ConcurrentDictionary<string, DeviceOptions> _deviceOptions; + + /// <summary> + /// Initializes a new instance of the <see cref="DeviceManager"/> class. + /// </summary> + /// <param name="dbProvider">The database provider.</param> + /// <param name="userManager">The user manager.</param> + public DeviceManager(IDbContextFactory<JellyfinDbContext> dbProvider, IUserManager userManager) + { + _dbProvider = dbProvider; + _userManager = userManager; + _devices = new ConcurrentDictionary<int, Device>(); + _deviceOptions = new ConcurrentDictionary<string, DeviceOptions>(); + + using var dbContext = _dbProvider.CreateDbContext(); + foreach (var device in dbContext.Devices + .OrderBy(d => d.Id) + .AsEnumerable()) + { + _devices.TryAdd(device.Id, device); + } + + foreach (var deviceOption in dbContext.DeviceOptions + .OrderBy(d => d.Id) + .AsEnumerable()) + { + _deviceOptions.TryAdd(deviceOption.DeviceId, deviceOption); + } + } + + /// <inheritdoc /> + public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>>? DeviceOptionsUpdated; + + /// <inheritdoc /> + public void SaveCapabilities(string deviceId, ClientCapabilities capabilities) + { + _capabilitiesMap[deviceId] = capabilities; + } + + /// <inheritdoc /> + public async Task UpdateDeviceOptions(string deviceId, string? deviceName) + { + DeviceOptions? deviceOptions; + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + deviceOptions = await dbContext.DeviceOptions.FirstOrDefaultAsync(dev => dev.DeviceId == deviceId).ConfigureAwait(false); + if (deviceOptions is null) + { + deviceOptions = new DeviceOptions(deviceId); + dbContext.DeviceOptions.Add(deviceOptions); + } + + deviceOptions.CustomName = deviceName; + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + + _deviceOptions[deviceId] = deviceOptions; + + DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs<Tuple<string, DeviceOptions>>(new Tuple<string, DeviceOptions>(deviceId, deviceOptions))); + } + + /// <inheritdoc /> + public async Task<Device> CreateDevice(Device device) + { + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + dbContext.Devices.Add(device); + await dbContext.SaveChangesAsync().ConfigureAwait(false); + _devices.TryAdd(device.Id, device); + } + + return device; + } + + /// <inheritdoc /> + public DeviceOptionsDto? GetDeviceOptions(string deviceId) + { + if (_deviceOptions.TryGetValue(deviceId, out var deviceOptions)) + { + return ToDeviceOptionsDto(deviceOptions); + } + + return null; + } + + /// <inheritdoc /> + public ClientCapabilities GetCapabilities(string? deviceId) + { + if (deviceId is null) + { + return new(); + } + + return _capabilitiesMap.TryGetValue(deviceId, out ClientCapabilities? result) + ? result + : new(); + } + + /// <inheritdoc /> + public DeviceInfoDto? GetDevice(string id) + { + var device = _devices.Values.Where(d => d.DeviceId == id).OrderByDescending(d => d.DateLastActivity).FirstOrDefault(); + _deviceOptions.TryGetValue(id, out var deviceOption); + + var deviceInfo = device is null ? null : ToDeviceInfo(device, deviceOption); + return deviceInfo is null ? null : ToDeviceInfoDto(deviceInfo); + } + + /// <inheritdoc /> + public QueryResult<Device> GetDevices(DeviceQuery query) + { + IEnumerable<Device> devices = _devices.Values + .Where(device => !query.UserId.HasValue || device.UserId.Equals(query.UserId.Value)) + .Where(device => query.DeviceId is null || device.DeviceId == query.DeviceId) + .Where(device => query.AccessToken is null || device.AccessToken == query.AccessToken) + .OrderBy(d => d.Id) + .ToList(); + var count = devices.Count(); + + if (query.Skip.HasValue) + { + devices = devices.Skip(query.Skip.Value); + } + + if (query.Limit.HasValue && query.Limit.Value > 0) + { + devices = devices.Take(query.Limit.Value); + } + + return new QueryResult<Device>(query.Skip, count, devices.ToList()); + } + + /// <inheritdoc /> + public QueryResult<DeviceInfo> GetDeviceInfos(DeviceQuery query) + { + var devices = GetDevices(query); + + return new QueryResult<DeviceInfo>( + devices.StartIndex, + devices.TotalRecordCount, + devices.Items.Select(device => ToDeviceInfo(device)).ToList()); + } + + /// <inheritdoc /> + public QueryResult<DeviceInfoDto> GetDevicesForUser(Guid? userId) + { + IEnumerable<Device> devices = _devices.Values + .OrderByDescending(d => d.DateLastActivity) + .ThenBy(d => d.DeviceId); + + if (!userId.IsNullOrEmpty()) + { + var user = _userManager.GetUserById(userId.Value); + if (user is null) + { + throw new ResourceNotFoundException(); + } + + devices = devices.Where(i => CanAccessDevice(user, i.DeviceId)); + } + + var array = devices.Select(device => + { + _deviceOptions.TryGetValue(device.DeviceId, out var option); + return ToDeviceInfo(device, option); + }) + .Select(ToDeviceInfoDto) + .ToArray(); + + return new QueryResult<DeviceInfoDto>(array); + } + + /// <inheritdoc /> + public async Task DeleteDevice(Device device) + { + _devices.TryRemove(device.Id, out _); + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + dbContext.Devices.Remove(device); + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + } + + /// <inheritdoc /> + public async Task UpdateDevice(Device device) + { + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + dbContext.Devices.Update(device); + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + + _devices[device.Id] = device; + } + + /// <inheritdoc /> + public bool CanAccessDevice(User user, string deviceId) + { + ArgumentNullException.ThrowIfNull(user); + ArgumentException.ThrowIfNullOrEmpty(deviceId); + + if (user.HasPermission(PermissionKind.EnableAllDevices) || user.HasPermission(PermissionKind.IsAdministrator)) + { + return true; + } + + return user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparison.OrdinalIgnoreCase) + || !GetCapabilities(deviceId).SupportsPersistentIdentifier; + } + + private DeviceInfo ToDeviceInfo(Device authInfo, DeviceOptions? options = null) + { + var caps = GetCapabilities(authInfo.DeviceId); + var user = _userManager.GetUserById(authInfo.UserId) ?? throw new ResourceNotFoundException("User with UserId " + authInfo.UserId + " not found"); + + return new() + { + AppName = authInfo.AppName, + AppVersion = authInfo.AppVersion, + Id = authInfo.DeviceId, + LastUserId = authInfo.UserId, + LastUserName = user.Username, + Name = authInfo.DeviceName, + DateLastActivity = authInfo.DateLastActivity, + IconUrl = caps.IconUrl, + CustomName = options?.CustomName, + }; + } + + private DeviceOptionsDto ToDeviceOptionsDto(DeviceOptions options) + { + return new() + { + Id = options.Id, + DeviceId = options.DeviceId, + CustomName = options.CustomName, + }; + } + + private DeviceInfoDto ToDeviceInfoDto(DeviceInfo info) + { + return new() + { + Name = info.Name, + CustomName = info.CustomName, + AccessToken = info.AccessToken, + Id = info.Id, + LastUserName = info.LastUserName, + AppName = info.AppName, + AppVersion = info.AppVersion, + LastUserId = info.LastUserId, + DateLastActivity = info.DateLastActivity, + Capabilities = ToClientCapabilitiesDto(info.Capabilities), + IconUrl = info.IconUrl + }; + } + + /// <inheritdoc /> + public ClientCapabilitiesDto ToClientCapabilitiesDto(ClientCapabilities capabilities) + { + return new() + { + PlayableMediaTypes = capabilities.PlayableMediaTypes, + SupportedCommands = capabilities.SupportedCommands, + SupportsMediaControl = capabilities.SupportsMediaControl, + SupportsPersistentIdentifier = capabilities.SupportsPersistentIdentifier, + DeviceProfile = capabilities.DeviceProfile, + AppStoreUrl = capabilities.AppStoreUrl, + IconUrl = capabilities.IconUrl + }; + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Library/LyricDownloadFailureLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Library/LyricDownloadFailureLogger.cs new file mode 100644 index 00000000..5f4864e9 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Library/LyricDownloadFailureLogger.cs @@ -0,0 +1,101 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Lyrics; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Library; + +/// <summary> +/// Creates an entry in the activity log whenever a lyric download fails. +/// </summary> +public class LyricDownloadFailureLogger : IEventConsumer<LyricDownloadFailureEventArgs> +{ + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="LyricDownloadFailureLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public LyricDownloadFailureLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(LyricDownloadFailureEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("LyricDownloadFailureFromForItem"), + eventArgs.Provider, + GetItemName(eventArgs.Item)), + "LyricDownloadFailure", + Guid.Empty) + { + ItemId = eventArgs.Item.Id.ToString("N", CultureInfo.InvariantCulture), + ShortOverview = eventArgs.Exception.Message + }).ConfigureAwait(false); + } + + private static string GetItemName(BaseItem item) + { + var name = item.Name; + if (item is Episode episode) + { + if (episode.IndexNumber.HasValue) + { + name = string.Format( + CultureInfo.InvariantCulture, + "Ep{0} - {1}", + episode.IndexNumber.Value, + name); + } + + if (episode.ParentIndexNumber.HasValue) + { + name = string.Format( + CultureInfo.InvariantCulture, + "S{0}, {1}", + episode.ParentIndexNumber.Value, + name); + } + } + + if (item is IHasSeries hasSeries) + { + name = hasSeries.SeriesName + " - " + name; + } + + if (item is IHasAlbumArtist hasAlbumArtist) + { + var artists = hasAlbumArtist.AlbumArtists; + + if (artists.Count > 0) + { + name = artists[0] + " - " + name; + } + } + else if (item is IHasArtist hasArtist) + { + var artists = hasArtist.Artists; + + if (artists.Count > 0) + { + name = artists[0] + " - " + name; + } + } + + return name; + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Library/SubtitleDownloadFailureLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Library/SubtitleDownloadFailureLogger.cs new file mode 100644 index 00000000..8fe380e4 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Library/SubtitleDownloadFailureLogger.cs @@ -0,0 +1,102 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Library +{ + /// <summary> + /// Creates an entry in the activity log whenever a subtitle download fails. + /// </summary> + public class SubtitleDownloadFailureLogger : IEventConsumer<SubtitleDownloadFailureEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="SubtitleDownloadFailureLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public SubtitleDownloadFailureLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(SubtitleDownloadFailureEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("SubtitleDownloadFailureFromForItem"), + eventArgs.Provider, + GetItemName(eventArgs.Item)), + "SubtitleDownloadFailure", + Guid.Empty) + { + ItemId = eventArgs.Item.Id.ToString("N", CultureInfo.InvariantCulture), + ShortOverview = eventArgs.Exception.Message + }).ConfigureAwait(false); + } + + private static string GetItemName(BaseItem item) + { + var name = item.Name; + if (item is Episode episode) + { + if (episode.IndexNumber.HasValue) + { + name = string.Format( + CultureInfo.InvariantCulture, + "Ep{0} - {1}", + episode.IndexNumber.Value, + name); + } + + if (episode.ParentIndexNumber.HasValue) + { + name = string.Format( + CultureInfo.InvariantCulture, + "S{0}, {1}", + episode.ParentIndexNumber.Value, + name); + } + } + + if (item is IHasSeries hasSeries) + { + name = hasSeries.SeriesName + " - " + name; + } + + if (item is IHasAlbumArtist hasAlbumArtist) + { + var artists = hasAlbumArtist.AlbumArtists; + + if (artists.Count > 0) + { + name = artists[0] + " - " + name; + } + } + else if (item is IHasArtist hasArtist) + { + var artists = hasArtist.Artists; + + if (artists.Count > 0) + { + name = artists[0] + " - " + name; + } + } + + return name; + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationFailedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationFailedLogger.cs new file mode 100644 index 00000000..1a8931a6 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationFailedLogger.cs @@ -0,0 +1,51 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Authentication; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Security +{ + /// <summary> + /// Creates an entry in the activity log when there is a failed login attempt. + /// </summary> + public class AuthenticationFailedLogger : IEventConsumer<AuthenticationRequestEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="AuthenticationFailedLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public AuthenticationFailedLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(AuthenticationRequestEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("FailedLoginAttemptWithUserName"), + eventArgs.Username), + "AuthenticationFailed", + Guid.Empty) + { + LogSeverity = LogLevel.Error, + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("LabelIpAddressValue"), + eventArgs.RemoteEndPoint), + }).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs new file mode 100644 index 00000000..584d559e --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs @@ -0,0 +1,48 @@ +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Authentication; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Security +{ + /// <summary> + /// Creates an entry in the activity log when there is a successful login attempt. + /// </summary> + public class AuthenticationSucceededLogger : IEventConsumer<AuthenticationResultEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="AuthenticationSucceededLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public AuthenticationSucceededLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(AuthenticationResultEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("AuthenticationSucceededWithUserName"), + eventArgs.User.Name), + "AuthenticationSucceeded", + eventArgs.User.Id) + { + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("LabelIpAddressValue"), + eventArgs.SessionInfo?.RemoteEndPoint), + }).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs new file mode 100644 index 00000000..73323acb --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs @@ -0,0 +1,108 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Notifications; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Session +{ + /// <summary> + /// Creates an entry in the activity log whenever a user starts playback. + /// </summary> + public class PlaybackStartLogger : IEventConsumer<PlaybackStartEventArgs> + { + private readonly ILogger<PlaybackStartLogger> _logger; + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PlaybackStartLogger"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public PlaybackStartLogger(ILogger<PlaybackStartLogger> logger, ILocalizationManager localizationManager, IActivityManager activityManager) + { + _logger = logger; + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PlaybackStartEventArgs eventArgs) + { + if (eventArgs.MediaInfo is null) + { + _logger.LogWarning("PlaybackStart reported with null media info."); + return; + } + + if (eventArgs.Item is not null && eventArgs.Item.IsThemeMedia) + { + // Don't report theme song or local trailer playback + return; + } + + if (eventArgs.Users.Count == 0) + { + return; + } + + var user = eventArgs.Users[0]; + + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("UserStartedPlayingItemWithValues"), + user.Username, + GetItemName(eventArgs.MediaInfo), + eventArgs.DeviceName), + GetPlaybackNotificationType(eventArgs.MediaInfo.MediaType), + user.Id) + { + ItemId = eventArgs.Item?.Id.ToString("N", CultureInfo.InvariantCulture), + }) + .ConfigureAwait(false); + } + + private static string GetItemName(BaseItemDto item) + { + var name = item.Name; + + if (!string.IsNullOrEmpty(item.SeriesName)) + { + name = item.SeriesName + " - " + name; + } + + if (item.Artists is not null && item.Artists.Count > 0) + { + name = item.Artists[0] + " - " + name; + } + + return name; + } + + private static string GetPlaybackNotificationType(MediaType mediaType) + { + if (mediaType == MediaType.Audio) + { + return NotificationType.AudioPlayback.ToString(); + } + + if (mediaType == MediaType.Video) + { + return NotificationType.VideoPlayback.ToString(); + } + + return "Playback"; + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs new file mode 100644 index 00000000..b7556753 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs @@ -0,0 +1,116 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Notifications; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Session +{ + /// <summary> + /// Creates an activity log entry whenever a user stops playback. + /// </summary> + public class PlaybackStopLogger : IEventConsumer<PlaybackStopEventArgs> + { + private readonly ILogger<PlaybackStopLogger> _logger; + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PlaybackStopLogger"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public PlaybackStopLogger(ILogger<PlaybackStopLogger> logger, ILocalizationManager localizationManager, IActivityManager activityManager) + { + _logger = logger; + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PlaybackStopEventArgs eventArgs) + { + var item = eventArgs.MediaInfo; + + if (item is null) + { + _logger.LogWarning("PlaybackStopped reported with null media info."); + return; + } + + if (eventArgs.Item is not null && eventArgs.Item.IsThemeMedia) + { + // Don't report theme song or local trailer playback + return; + } + + if (eventArgs.Users.Count == 0) + { + return; + } + + var user = eventArgs.Users[0]; + + var notificationType = GetPlaybackStoppedNotificationType(item.MediaType); + if (notificationType is null) + { + return; + } + + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("UserStoppedPlayingItemWithValues"), + user.Username, + GetItemName(item), + eventArgs.DeviceName), + notificationType, + user.Id) + { + ItemId = eventArgs.Item?.Id.ToString("N", CultureInfo.InvariantCulture), + }) + .ConfigureAwait(false); + } + + private static string GetItemName(BaseItemDto item) + { + var name = item.Name; + + if (!string.IsNullOrEmpty(item.SeriesName)) + { + name = item.SeriesName + " - " + name; + } + + if (item.Artists is not null && item.Artists.Count > 0) + { + name = item.Artists[0] + " - " + name; + } + + return name; + } + + private static string? GetPlaybackStoppedNotificationType(MediaType mediaType) + { + if (mediaType == MediaType.Audio) + { + return NotificationType.AudioPlaybackStopped.ToString(); + } + + if (mediaType == MediaType.Video) + { + return NotificationType.VideoPlaybackStopped.ToString(); + } + + return null; + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/SessionEndedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/SessionEndedLogger.cs new file mode 100644 index 00000000..b90708a2 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/SessionEndedLogger.cs @@ -0,0 +1,54 @@ +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Session; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Session +{ + /// <summary> + /// Creates an entry in the activity log whenever a session ends. + /// </summary> + public class SessionEndedLogger : IEventConsumer<SessionEndedEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="SessionEndedLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public SessionEndedLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(SessionEndedEventArgs eventArgs) + { + if (string.IsNullOrEmpty(eventArgs.Argument.UserName)) + { + return; + } + + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("UserOfflineFromDevice"), + eventArgs.Argument.UserName, + eventArgs.Argument.DeviceName), + "SessionEnded", + eventArgs.Argument.UserId) + { + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("LabelIpAddressValue"), + eventArgs.Argument.RemoteEndPoint), + }).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/SessionStartedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/SessionStartedLogger.cs new file mode 100644 index 00000000..139c2e2a --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/SessionStartedLogger.cs @@ -0,0 +1,54 @@ +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Session; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Session +{ + /// <summary> + /// Creates an entry in the activity log when a session is started. + /// </summary> + public class SessionStartedLogger : IEventConsumer<SessionStartedEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="SessionStartedLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public SessionStartedLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(SessionStartedEventArgs eventArgs) + { + if (string.IsNullOrEmpty(eventArgs.Argument.UserName)) + { + return; + } + + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("UserOnlineFromDevice"), + eventArgs.Argument.UserName, + eventArgs.Argument.DeviceName), + "SessionStarted", + eventArgs.Argument.UserId) + { + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("LabelIpAddressValue"), + eventArgs.Argument.RemoteEndPoint) + }).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/System/PendingRestartNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/System/PendingRestartNotifier.cs new file mode 100644 index 00000000..970df3a9 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/System/PendingRestartNotifier.cs @@ -0,0 +1,31 @@ +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Events.System; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Session; + +namespace Jellyfin.Server.Implementations.Events.Consumers.System +{ + /// <summary> + /// Notifies users when there is a pending restart. + /// </summary> + public class PendingRestartNotifier : IEventConsumer<PendingRestartEventArgs> + { + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PendingRestartNotifier"/> class. + /// </summary> + /// <param name="sessionManager">The session manager.</param> + public PendingRestartNotifier(ISessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PendingRestartEventArgs eventArgs) + { + await _sessionManager.SendRestartRequiredNotification(CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs new file mode 100644 index 00000000..da82a3b3 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Notifications; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.Events.Consumers.System +{ + /// <summary> + /// Creates an activity log entry whenever a task is completed. + /// </summary> + public class TaskCompletedLogger : IEventConsumer<TaskCompletionEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="TaskCompletedLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public TaskCompletedLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(TaskCompletionEventArgs eventArgs) + { + var result = eventArgs.Result; + var task = eventArgs.Task; + + if (task.ScheduledTask is IConfigurableScheduledTask activityTask + && !activityTask.IsLogged) + { + return; + } + + var time = result.EndTimeUtc - result.StartTimeUtc; + var runningTime = string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("LabelRunningTimeValue"), + ToUserFriendlyString(time)); + + if (result.Status == TaskCompletionStatus.Failed) + { + var vals = new List<string>(); + + if (!string.IsNullOrEmpty(eventArgs.Result.ErrorMessage)) + { + vals.Add(eventArgs.Result.ErrorMessage); + } + + if (!string.IsNullOrEmpty(eventArgs.Result.LongErrorMessage)) + { + vals.Add(eventArgs.Result.LongErrorMessage); + } + + await _activityManager.CreateAsync(new ActivityLog( + string.Format(CultureInfo.InvariantCulture, _localizationManager.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name), + NotificationType.TaskFailed.ToString(), + Guid.Empty) + { + LogSeverity = LogLevel.Error, + Overview = string.Join(Environment.NewLine, vals), + ShortOverview = runningTime + }).ConfigureAwait(false); + } + } + + private static string ToUserFriendlyString(TimeSpan span) + { + const int DaysInYear = 365; + const int DaysInMonth = 30; + + // Get each non-zero value from TimeSpan component + var values = new List<string>(); + + // Number of years + int days = span.Days; + if (days >= DaysInYear) + { + int years = days / DaysInYear; + values.Add(CreateValueString(years, "year")); + days %= DaysInYear; + } + + // Number of months + if (days >= DaysInMonth) + { + int months = days / DaysInMonth; + values.Add(CreateValueString(months, "month")); + days = days % DaysInMonth; + } + + // Number of days + if (days >= 1) + { + values.Add(CreateValueString(days, "day")); + } + + // Number of hours + if (span.Hours >= 1) + { + values.Add(CreateValueString(span.Hours, "hour")); + } + + // Number of minutes + if (span.Minutes >= 1) + { + values.Add(CreateValueString(span.Minutes, "minute")); + } + + // Number of seconds (include when 0 if no other components included) + if (span.Seconds >= 1 || values.Count == 0) + { + values.Add(CreateValueString(span.Seconds, "second")); + } + + // Combine values into string + var builder = new StringBuilder(); + for (int i = 0; i < values.Count; i++) + { + if (builder.Length > 0) + { + builder.Append(i == values.Count - 1 ? " and " : ", "); + } + + builder.Append(values[i]); + } + + // Return result + return builder.ToString(); + } + + /// <summary> + /// Constructs a string description of a time-span value. + /// </summary> + /// <param name="value">The value of this item.</param> + /// <param name="description">The name of this item (singular form).</param> + private static string CreateValueString(int value, string description) + { + return string.Format( + CultureInfo.InvariantCulture, + "{0:#,##0} {1}", + value, + value == 1 ? description : string.Format(CultureInfo.InvariantCulture, "{0}s", description)); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs new file mode 100644 index 00000000..aebe7496 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs @@ -0,0 +1,32 @@ +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; +using MediaBrowser.Model.Tasks; + +namespace Jellyfin.Server.Implementations.Events.Consumers.System +{ + /// <summary> + /// Notifies admin users when a task is completed. + /// </summary> + public class TaskCompletedNotifier : IEventConsumer<TaskCompletionEventArgs> + { + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="TaskCompletedNotifier"/> class. + /// </summary> + /// <param name="sessionManager">The session manager.</param> + public TaskCompletedNotifier(ISessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public async Task OnEvent(TaskCompletionEventArgs eventArgs) + { + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.ScheduledTaskEnded, eventArgs.Result, CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs new file mode 100644 index 00000000..10602538 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs @@ -0,0 +1,32 @@ +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Updates; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Updates +{ + /// <summary> + /// Notifies admin users when a plugin installation is cancelled. + /// </summary> + public class PluginInstallationCancelledNotifier : IEventConsumer<PluginInstallationCancelledEventArgs> + { + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginInstallationCancelledNotifier"/> class. + /// </summary> + /// <param name="sessionManager">The session manager.</param> + public PluginInstallationCancelledNotifier(ISessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PluginInstallationCancelledEventArgs eventArgs) + { + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstallationCancelled, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedLogger.cs new file mode 100644 index 00000000..632f30c7 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedLogger.cs @@ -0,0 +1,51 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller.Events; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Notifications; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Updates +{ + /// <summary> + /// Creates an entry in the activity log when a package installation fails. + /// </summary> + public class PluginInstallationFailedLogger : IEventConsumer<InstallationFailedEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginInstallationFailedLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public PluginInstallationFailedLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(InstallationFailedEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("NameInstallFailed"), + eventArgs.InstallationInfo.Name), + NotificationType.InstallationFailed.ToString(), + Guid.Empty) + { + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("VersionNumber"), + eventArgs.InstallationInfo.Version), + Overview = eventArgs.Exception.Message + }).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs new file mode 100644 index 00000000..23135c0e --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs @@ -0,0 +1,32 @@ +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Updates +{ + /// <summary> + /// Notifies admin users when a plugin installation fails. + /// </summary> + public class PluginInstallationFailedNotifier : IEventConsumer<InstallationFailedEventArgs> + { + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginInstallationFailedNotifier"/> class. + /// </summary> + /// <param name="sessionManager">The session manager.</param> + public PluginInstallationFailedNotifier(ISessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public async Task OnEvent(InstallationFailedEventArgs eventArgs) + { + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstallationFailed, eventArgs.InstallationInfo, CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledLogger.cs new file mode 100644 index 00000000..4b49b714 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledLogger.cs @@ -0,0 +1,50 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Updates; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Notifications; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Updates +{ + /// <summary> + /// Creates an entry in the activity log when a plugin is installed. + /// </summary> + public class PluginInstalledLogger : IEventConsumer<PluginInstalledEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginInstalledLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public PluginInstalledLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PluginInstalledEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("PluginInstalledWithName"), + eventArgs.Argument.Name), + NotificationType.PluginInstalled.ToString(), + Guid.Empty) + { + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("VersionNumber"), + eventArgs.Argument.Version) + }).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs new file mode 100644 index 00000000..76d7c62e --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs @@ -0,0 +1,32 @@ +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Updates; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Updates +{ + /// <summary> + /// Notifies admin users when a plugin is installed. + /// </summary> + public class PluginInstalledNotifier : IEventConsumer<PluginInstalledEventArgs> + { + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginInstalledNotifier"/> class. + /// </summary> + /// <param name="sessionManager">The session manager.</param> + public PluginInstalledNotifier(ISessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PluginInstalledEventArgs eventArgs) + { + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstallationCompleted, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs new file mode 100644 index 00000000..e343b33e --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs @@ -0,0 +1,32 @@ +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Updates; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Updates +{ + /// <summary> + /// Notifies admin users when a plugin is being installed. + /// </summary> + public class PluginInstallingNotifier : IEventConsumer<PluginInstallingEventArgs> + { + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginInstallingNotifier"/> class. + /// </summary> + /// <param name="sessionManager">The session manager.</param> + public PluginInstallingNotifier(ISessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PluginInstallingEventArgs eventArgs) + { + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstalling, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs new file mode 100644 index 00000000..2d24de7f --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs @@ -0,0 +1,45 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Updates; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Notifications; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Updates +{ + /// <summary> + /// Creates an entry in the activity log when a plugin is uninstalled. + /// </summary> + public class PluginUninstalledLogger : IEventConsumer<PluginUninstalledEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginUninstalledLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public PluginUninstalledLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PluginUninstalledEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("PluginUninstalledWithName"), + eventArgs.Argument.Name), + NotificationType.PluginUninstalled.ToString(), + Guid.Empty)) + .ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs new file mode 100644 index 00000000..ed487d8a --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs @@ -0,0 +1,32 @@ +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Updates; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Updates +{ + /// <summary> + /// Notifies admin users when a plugin is uninstalled. + /// </summary> + public class PluginUninstalledNotifier : IEventConsumer<PluginUninstalledEventArgs> + { + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginUninstalledNotifier"/> class. + /// </summary> + /// <param name="sessionManager">The session manager.</param> + public PluginUninstalledNotifier(ISessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PluginUninstalledEventArgs eventArgs) + { + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageUninstalled, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUpdatedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUpdatedLogger.cs new file mode 100644 index 00000000..e892d3dd --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUpdatedLogger.cs @@ -0,0 +1,51 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Updates; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Notifications; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Updates +{ + /// <summary> + /// Creates an entry in the activity log when a plugin is updated. + /// </summary> + public class PluginUpdatedLogger : IEventConsumer<PluginUpdatedEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginUpdatedLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public PluginUpdatedLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PluginUpdatedEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("PluginUpdatedWithName"), + eventArgs.Argument.Name), + NotificationType.PluginUpdateInstalled.ToString(), + Guid.Empty) + { + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("VersionNumber"), + eventArgs.Argument.Version), + Overview = eventArgs.Argument.Changelog + }).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserCreatedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserCreatedLogger.cs new file mode 100644 index 00000000..4f063f6a --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserCreatedLogger.cs @@ -0,0 +1,43 @@ +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Events.Users; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Users +{ + /// <summary> + /// Creates an entry in the activity log when a user is created. + /// </summary> + public class UserCreatedLogger : IEventConsumer<UserCreatedEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="UserCreatedLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public UserCreatedLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(UserCreatedEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("UserCreatedWithName"), + eventArgs.Argument.Username), + "UserCreated", + eventArgs.Argument.Id)) + .ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedLogger.cs new file mode 100644 index 00000000..ba4a072e --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedLogger.cs @@ -0,0 +1,44 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Events.Users; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Users +{ + /// <summary> + /// Adds an entry to the activity log when a user is deleted. + /// </summary> + public class UserDeletedLogger : IEventConsumer<UserDeletedEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="UserDeletedLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public UserDeletedLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(UserDeletedEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("UserDeletedWithName"), + eventArgs.Argument.Username), + "UserDeleted", + Guid.Empty)) + .ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs new file mode 100644 index 00000000..8eefc03d --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Events.Users; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Users +{ + /// <summary> + /// Notifies the user's sessions when a user is deleted. + /// </summary> + public class UserDeletedNotifier : IEventConsumer<UserDeletedEventArgs> + { + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="UserDeletedNotifier"/> class. + /// </summary> + /// <param name="sessionManager">The session manager.</param> + public UserDeletedNotifier(ISessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public async Task OnEvent(UserDeletedEventArgs eventArgs) + { + await _sessionManager.SendMessageToUserSessions( + new List<Guid> { eventArgs.Argument.Id }, + SessionMessageType.UserDeleted, + eventArgs.Argument.Id.ToString("N", CultureInfo.InvariantCulture), + CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserLockedOutLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserLockedOutLogger.cs new file mode 100644 index 00000000..bbc00567 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserLockedOutLogger.cs @@ -0,0 +1,47 @@ +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Events.Users; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Notifications; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Users +{ + /// <summary> + /// Creates an entry in the activity log when a user is locked out. + /// </summary> + public class UserLockedOutLogger : IEventConsumer<UserLockedOutEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="UserLockedOutLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public UserLockedOutLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(UserLockedOutEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("UserLockedOutWithName"), + eventArgs.Argument.Username), + NotificationType.UserLockedOut.ToString(), + eventArgs.Argument.Id) + { + LogSeverity = LogLevel.Error + }).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserPasswordChangedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserPasswordChangedLogger.cs new file mode 100644 index 00000000..7219704e --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserPasswordChangedLogger.cs @@ -0,0 +1,43 @@ +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Events.Users; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Users +{ + /// <summary> + /// Creates an entry in the activity log when a user's password is changed. + /// </summary> + public class UserPasswordChangedLogger : IEventConsumer<UserPasswordChangedEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="UserPasswordChangedLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public UserPasswordChangedLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(UserPasswordChangedEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("UserPasswordChangedWithName"), + eventArgs.Argument.Username), + "UserPasswordChanged", + eventArgs.Argument.Id)) + .ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs new file mode 100644 index 00000000..0af5cbb6 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Events.Users; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Users +{ + /// <summary> + /// Notifies a user when their account has been updated. + /// </summary> + public class UserUpdatedNotifier : IEventConsumer<UserUpdatedEventArgs> + { + private readonly IUserManager _userManager; + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="UserUpdatedNotifier"/> class. + /// </summary> + /// <param name="userManager">The user manager.</param> + /// <param name="sessionManager">The session manager.</param> + public UserUpdatedNotifier(IUserManager userManager, ISessionManager sessionManager) + { + _userManager = userManager; + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public async Task OnEvent(UserUpdatedEventArgs eventArgs) + { + await _sessionManager.SendMessageToUserSessions( + new List<Guid> { eventArgs.Argument.Id }, + SessionMessageType.UserUpdated, + _userManager.GetUserDto(eventArgs.Argument), + CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/EventManager.cs b/Jellyfin.Server.Implementations/Events/EventManager.cs new file mode 100644 index 00000000..39235a1f --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/EventManager.cs @@ -0,0 +1,65 @@ +using System; +using System.Threading.Tasks; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Events; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.Events +{ + /// <summary> + /// Handles the firing of events. + /// </summary> + public class EventManager : IEventManager + { + private readonly ILogger<EventManager> _logger; + private readonly IServerApplicationHost _appHost; + + /// <summary> + /// Initializes a new instance of the <see cref="EventManager"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="appHost">The application host.</param> + public EventManager(ILogger<EventManager> logger, IServerApplicationHost appHost) + { + _logger = logger; + _appHost = appHost; + } + + /// <inheritdoc /> + public void Publish<T>(T eventArgs) + where T : EventArgs + { + PublishInternal(eventArgs).GetAwaiter().GetResult(); + } + + /// <inheritdoc /> + public async Task PublishAsync<T>(T eventArgs) + where T : EventArgs + { + await PublishInternal(eventArgs).ConfigureAwait(false); + } + + private async Task PublishInternal<T>(T eventArgs) + where T : EventArgs + { + using var scope = _appHost.ServiceProvider?.CreateScope(); + if (scope is null) + { + return; + } + + foreach (var service in scope.ServiceProvider.GetServices<IEventConsumer<T>>()) + { + try + { + await service.OnEvent(eventArgs).ConfigureAwait(false); + } + catch (Exception e) + { + _logger.LogError(e, "Uncaught exception in EventConsumer {Type}: ", service.GetType()); + } + } + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/EventingServiceCollectionExtensions.cs b/Jellyfin.Server.Implementations/Events/EventingServiceCollectionExtensions.cs new file mode 100644 index 00000000..87de4ed7 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/EventingServiceCollectionExtensions.cs @@ -0,0 +1,72 @@ +using Jellyfin.Data.Events.System; +using Jellyfin.Data.Events.Users; +using Jellyfin.Server.Implementations.Events.Consumers.Library; +using Jellyfin.Server.Implementations.Events.Consumers.Security; +using Jellyfin.Server.Implementations.Events.Consumers.Session; +using Jellyfin.Server.Implementations.Events.Consumers.System; +using Jellyfin.Server.Implementations.Events.Consumers.Updates; +using Jellyfin.Server.Implementations.Events.Consumers.Users; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Authentication; +using MediaBrowser.Controller.Events.Session; +using MediaBrowser.Controller.Events.Updates; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Lyrics; +using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace Jellyfin.Server.Implementations.Events +{ + /// <summary> + /// A class containing extensions to <see cref="IServiceCollection"/> for eventing. + /// </summary> + public static class EventingServiceCollectionExtensions + { + /// <summary> + /// Adds the event services to the service collection. + /// </summary> + /// <param name="collection">The service collection.</param> + public static void AddEventServices(this IServiceCollection collection) + { + // Library consumers + collection.AddScoped<IEventConsumer<LyricDownloadFailureEventArgs>, LyricDownloadFailureLogger>(); + collection.AddScoped<IEventConsumer<SubtitleDownloadFailureEventArgs>, SubtitleDownloadFailureLogger>(); + + // Security consumers + collection.AddScoped<IEventConsumer<AuthenticationRequestEventArgs>, AuthenticationFailedLogger>(); + collection.AddScoped<IEventConsumer<AuthenticationResultEventArgs>, AuthenticationSucceededLogger>(); + + // Session consumers + collection.AddScoped<IEventConsumer<PlaybackStartEventArgs>, PlaybackStartLogger>(); + collection.AddScoped<IEventConsumer<PlaybackStopEventArgs>, PlaybackStopLogger>(); + collection.AddScoped<IEventConsumer<SessionEndedEventArgs>, SessionEndedLogger>(); + collection.AddScoped<IEventConsumer<SessionStartedEventArgs>, SessionStartedLogger>(); + + // System consumers + collection.AddScoped<IEventConsumer<PendingRestartEventArgs>, PendingRestartNotifier>(); + collection.AddScoped<IEventConsumer<TaskCompletionEventArgs>, TaskCompletedLogger>(); + collection.AddScoped<IEventConsumer<TaskCompletionEventArgs>, TaskCompletedNotifier>(); + + // Update consumers + collection.AddScoped<IEventConsumer<PluginInstallationCancelledEventArgs>, PluginInstallationCancelledNotifier>(); + collection.AddScoped<IEventConsumer<InstallationFailedEventArgs>, PluginInstallationFailedLogger>(); + collection.AddScoped<IEventConsumer<InstallationFailedEventArgs>, PluginInstallationFailedNotifier>(); + collection.AddScoped<IEventConsumer<PluginInstalledEventArgs>, PluginInstalledLogger>(); + collection.AddScoped<IEventConsumer<PluginInstalledEventArgs>, PluginInstalledNotifier>(); + collection.AddScoped<IEventConsumer<PluginInstallingEventArgs>, PluginInstallingNotifier>(); + collection.AddScoped<IEventConsumer<PluginUninstalledEventArgs>, PluginUninstalledLogger>(); + collection.AddScoped<IEventConsumer<PluginUninstalledEventArgs>, PluginUninstalledNotifier>(); + collection.AddScoped<IEventConsumer<PluginUpdatedEventArgs>, PluginUpdatedLogger>(); + + // User consumers + collection.AddScoped<IEventConsumer<UserCreatedEventArgs>, UserCreatedLogger>(); + collection.AddScoped<IEventConsumer<UserDeletedEventArgs>, UserDeletedLogger>(); + collection.AddScoped<IEventConsumer<UserDeletedEventArgs>, UserDeletedNotifier>(); + collection.AddScoped<IEventConsumer<UserLockedOutEventArgs>, UserLockedOutLogger>(); + collection.AddScoped<IEventConsumer<UserPasswordChangedEventArgs>, UserPasswordChangedLogger>(); + collection.AddScoped<IEventConsumer<UserUpdatedEventArgs>, UserUpdatedNotifier>(); + } + } +} diff --git a/Jellyfin.Server.Implementations/Extensions/ExpressionExtensions.cs b/Jellyfin.Server.Implementations/Extensions/ExpressionExtensions.cs new file mode 100644 index 00000000..d70ac672 --- /dev/null +++ b/Jellyfin.Server.Implementations/Extensions/ExpressionExtensions.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Jellyfin.Server.Implementations.Extensions; + +/// <summary> +/// Provides <see cref="Expression"/> extension methods. +/// </summary> +public static class ExpressionExtensions +{ + /// <summary> + /// Combines two predicates into a single predicate using a logical OR operation. + /// </summary> + /// <typeparam name="T">The predicate parameter type.</typeparam> + /// <param name="firstPredicate">The first predicate expression to combine.</param> + /// <param name="secondPredicate">The second predicate expression to combine.</param> + /// <returns>A new expression representing the OR combination of the input predicates.</returns> + public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> firstPredicate, Expression<Func<T, bool>> secondPredicate) + { + ArgumentNullException.ThrowIfNull(firstPredicate); + ArgumentNullException.ThrowIfNull(secondPredicate); + + var invokedExpression = Expression.Invoke(secondPredicate, firstPredicate.Parameters); + return Expression.Lambda<Func<T, bool>>(Expression.OrElse(firstPredicate.Body, invokedExpression), firstPredicate.Parameters); + } + + /// <summary> + /// Combines multiple predicates into a single predicate using a logical OR operation. + /// </summary> + /// <typeparam name="T">The predicate parameter type.</typeparam> + /// <param name="predicates">A collection of predicate expressions to combine.</param> + /// <returns>A new expression representing the OR combination of all input predicates.</returns> + public static Expression<Func<T, bool>> Or<T>(this IEnumerable<Expression<Func<T, bool>>> predicates) + { + ArgumentNullException.ThrowIfNull(predicates); + + return predicates.Aggregate((aggregatePredicate, nextPredicate) => aggregatePredicate.Or(nextPredicate)); + } + + /// <summary> + /// Combines two predicates into a single predicate using a logical AND operation. + /// </summary> + /// <typeparam name="T">The predicate parameter type.</typeparam> + /// <param name="firstPredicate">The first predicate expression to combine.</param> + /// <param name="secondPredicate">The second predicate expression to combine.</param> + /// <returns>A new expression representing the AND combination of the input predicates.</returns> + public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> firstPredicate, Expression<Func<T, bool>> secondPredicate) + { + ArgumentNullException.ThrowIfNull(firstPredicate); + ArgumentNullException.ThrowIfNull(secondPredicate); + + var invokedExpression = Expression.Invoke(secondPredicate, firstPredicate.Parameters); + return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(firstPredicate.Body, invokedExpression), firstPredicate.Parameters); + } + + /// <summary> + /// Combines multiple predicates into a single predicate using a logical AND operation. + /// </summary> + /// <typeparam name="T">The predicate parameter type.</typeparam> + /// <param name="predicates">A collection of predicate expressions to combine.</param> + /// <returns>A new expression representing the AND combination of all input predicates.</returns> + public static Expression<Func<T, bool>> And<T>(this IEnumerable<Expression<Func<T, bool>>> predicates) + { + ArgumentNullException.ThrowIfNull(predicates); + + return predicates.Aggregate((aggregatePredicate, nextPredicate) => aggregatePredicate.And(nextPredicate)); + } +} diff --git a/Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs b/Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..932f9d62 --- /dev/null +++ b/Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.DbConfiguration; +using Jellyfin.Database.Implementations.Locking; +using Jellyfin.Database.Providers.Sqlite; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Configuration; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using JellyfinDbProviderFactory = System.Func<System.IServiceProvider, Jellyfin.Database.Implementations.IJellyfinDatabaseProvider>; + +namespace Jellyfin.Server.Implementations.Extensions; + +/// <summary> +/// Extensions for the <see cref="IServiceCollection"/> interface. +/// </summary> +public static class ServiceCollectionExtensions +{ + private static IEnumerable<Type> DatabaseProviderTypes() + { + yield return typeof(SqliteDatabaseProvider); + } + + private static IDictionary<string, JellyfinDbProviderFactory> GetSupportedDbProviders() + { + var items = new Dictionary<string, JellyfinDbProviderFactory>(StringComparer.InvariantCultureIgnoreCase); + foreach (var providerType in DatabaseProviderTypes()) + { + var keyAttribute = providerType.GetCustomAttribute<JellyfinDatabaseProviderKeyAttribute>(); + if (keyAttribute is null || string.IsNullOrWhiteSpace(keyAttribute.DatabaseProviderKey)) + { + continue; + } + + var provider = providerType; + items[keyAttribute.DatabaseProviderKey] = (services) => (IJellyfinDatabaseProvider)ActivatorUtilities.CreateInstance(services, providerType); + } + + return items; + } + + private static JellyfinDbProviderFactory? LoadDatabasePlugin(CustomDatabaseOptions customProviderOptions, IApplicationPaths applicationPaths) + { + var plugin = Directory.EnumerateDirectories(applicationPaths.PluginsPath) + .Where(e => Path.GetFileName(e)!.StartsWith(customProviderOptions.PluginName, StringComparison.OrdinalIgnoreCase)) + .Order() + .FirstOrDefault() + ?? throw new InvalidOperationException($"The requested custom database plugin with the name '{customProviderOptions.PluginName}' could not been found in '{applicationPaths.PluginsPath}'"); + + var dbProviderAssembly = Path.Combine(plugin, Path.ChangeExtension(customProviderOptions.PluginAssembly, "dll")); + if (!File.Exists(dbProviderAssembly)) + { + throw new InvalidOperationException($"Could not find the requested assembly at '{dbProviderAssembly}'"); + } + + // we have to load the assembly without proxy to ensure maximum performance for this. + var assembly = Assembly.LoadFrom(dbProviderAssembly); + var dbProviderType = assembly.GetExportedTypes().FirstOrDefault(f => f.IsAssignableTo(typeof(IJellyfinDatabaseProvider))) + ?? throw new InvalidOperationException($"Could not find any type implementing the '{nameof(IJellyfinDatabaseProvider)}' interface."); + + return (services) => (IJellyfinDatabaseProvider)ActivatorUtilities.CreateInstance(services, dbProviderType); + } + + /// <summary> + /// Adds the <see cref="IDbContextFactory{TContext}"/> interface to the service collection with second level caching enabled. + /// </summary> + /// <param name="serviceCollection">An instance of the <see cref="IServiceCollection"/> interface.</param> + /// <param name="configurationManager">The server configuration manager.</param> + /// <param name="configuration">The startup Configuration.</param> + /// <returns>The updated service collection.</returns> + public static IServiceCollection AddJellyfinDbContext( + this IServiceCollection serviceCollection, + IServerConfigurationManager configurationManager, + IConfiguration configuration) + { + var efCoreConfiguration = configurationManager.GetConfiguration<DatabaseConfigurationOptions>("database"); + JellyfinDbProviderFactory? providerFactory = null; + + if (efCoreConfiguration?.DatabaseType is null) + { + var cmdMigrationArgument = configuration.GetValue<string>("migration-provider"); + if (!string.IsNullOrWhiteSpace(cmdMigrationArgument)) + { + efCoreConfiguration = new DatabaseConfigurationOptions() + { + DatabaseType = cmdMigrationArgument, + }; + } + else + { + // when nothing is setup via new Database configuration, fallback to SQLite with default settings. + efCoreConfiguration = new DatabaseConfigurationOptions() + { + DatabaseType = "Jellyfin-SQLite", + LockingBehavior = DatabaseLockingBehaviorTypes.NoLock + }; + configurationManager.SaveConfiguration("database", efCoreConfiguration); + } + } + + if (efCoreConfiguration.DatabaseType.Equals("PLUGIN_PROVIDER", StringComparison.OrdinalIgnoreCase)) + { + if (efCoreConfiguration.CustomProviderOptions is null) + { + throw new InvalidOperationException("The custom database provider must declare the custom provider options to work"); + } + + providerFactory = LoadDatabasePlugin(efCoreConfiguration.CustomProviderOptions, configurationManager.ApplicationPaths); + } + else + { + var providers = GetSupportedDbProviders(); + if (!providers.TryGetValue(efCoreConfiguration.DatabaseType.ToUpperInvariant(), out providerFactory!)) + { + throw new InvalidOperationException($"Jellyfin cannot find the database provider of type '{efCoreConfiguration.DatabaseType}'. Supported types are {string.Join(", ", providers.Keys)}"); + } + } + + serviceCollection.AddSingleton<IJellyfinDatabaseProvider>(providerFactory!); + + switch (efCoreConfiguration.LockingBehavior) + { + case DatabaseLockingBehaviorTypes.NoLock: + serviceCollection.AddSingleton<IEntityFrameworkCoreLockingBehavior, NoLockBehavior>(); + break; + case DatabaseLockingBehaviorTypes.Pessimistic: + serviceCollection.AddSingleton<IEntityFrameworkCoreLockingBehavior, PessimisticLockBehavior>(); + break; + case DatabaseLockingBehaviorTypes.Optimistic: + serviceCollection.AddSingleton<IEntityFrameworkCoreLockingBehavior, OptimisticLockBehavior>(); + break; + } + + serviceCollection.AddPooledDbContextFactory<JellyfinDbContext>((serviceProvider, opt) => + { + var provider = serviceProvider.GetRequiredService<IJellyfinDatabaseProvider>(); + provider.Initialise(opt, efCoreConfiguration); + var lockingBehavior = serviceProvider.GetRequiredService<IEntityFrameworkCoreLockingBehavior>(); + lockingBehavior.Initialise(opt); + }); + + return serviceCollection; + } +} diff --git a/Jellyfin.Server.Implementations/FullSystemBackup/BackupManifest.cs b/Jellyfin.Server.Implementations/FullSystemBackup/BackupManifest.cs new file mode 100644 index 00000000..77a49b2b --- /dev/null +++ b/Jellyfin.Server.Implementations/FullSystemBackup/BackupManifest.cs @@ -0,0 +1,19 @@ +using System; + +namespace Jellyfin.Server.Implementations.FullSystemBackup; + +/// <summary> +/// Manifest type for backups internal structure. +/// </summary> +internal class BackupManifest +{ + public required Version ServerVersion { get; set; } + + public required Version BackupEngineVersion { get; set; } + + public required DateTimeOffset DateCreated { get; set; } + + public required string[] DatabaseTables { get; set; } + + public required BackupOptions Options { get; set; } +} diff --git a/Jellyfin.Server.Implementations/FullSystemBackup/BackupOptions.cs b/Jellyfin.Server.Implementations/FullSystemBackup/BackupOptions.cs new file mode 100644 index 00000000..8bd108c4 --- /dev/null +++ b/Jellyfin.Server.Implementations/FullSystemBackup/BackupOptions.cs @@ -0,0 +1,15 @@ +namespace Jellyfin.Server.Implementations.FullSystemBackup; + +/// <summary> +/// Defines the optional contents of the backup archive. +/// </summary> +internal class BackupOptions +{ + public bool Metadata { get; set; } + + public bool Trickplay { get; set; } + + public bool Subtitles { get; set; } + + public bool Database { get; set; } +} diff --git a/Jellyfin.Server.Implementations/FullSystemBackup/BackupService.cs b/Jellyfin.Server.Implementations/FullSystemBackup/BackupService.cs new file mode 100644 index 00000000..30094a88 --- /dev/null +++ b/Jellyfin.Server.Implementations/FullSystemBackup/BackupService.cs @@ -0,0 +1,560 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations; +using Jellyfin.Server.Implementations.StorageHelpers; +using Jellyfin.Server.Implementations.SystemBackupService; +using MediaBrowser.Controller; +using MediaBrowser.Controller.SystemBackupService; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.FullSystemBackup; + +/// <summary> +/// Contains methods for creating and restoring backups. +/// </summary> +public class BackupService : IBackupService +{ + private const string ManifestEntryName = "manifest.json"; + private readonly ILogger<BackupService> _logger; + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + private readonly IServerApplicationHost _applicationHost; + private readonly IServerApplicationPaths _applicationPaths; + private readonly IJellyfinDatabaseProvider _jellyfinDatabaseProvider; + private readonly IHostApplicationLifetime _hostApplicationLifetime; + private static readonly JsonSerializerOptions _serializerSettings = new JsonSerializerOptions(JsonSerializerDefaults.General) + { + AllowTrailingCommas = true, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + }; + + private readonly Version _backupEngineVersion = new Version(0, 2, 0); + + /// <summary> + /// Initializes a new instance of the <see cref="BackupService"/> class. + /// </summary> + /// <param name="logger">A logger.</param> + /// <param name="dbProvider">A Database Factory.</param> + /// <param name="applicationHost">The Application host.</param> + /// <param name="applicationPaths">The application paths.</param> + /// <param name="jellyfinDatabaseProvider">The Jellyfin database Provider in use.</param> + /// <param name="applicationLifetime">The SystemManager.</param> + public BackupService( + ILogger<BackupService> logger, + IDbContextFactory<JellyfinDbContext> dbProvider, + IServerApplicationHost applicationHost, + IServerApplicationPaths applicationPaths, + IJellyfinDatabaseProvider jellyfinDatabaseProvider, + IHostApplicationLifetime applicationLifetime) + { + _logger = logger; + _dbProvider = dbProvider; + _applicationHost = applicationHost; + _applicationPaths = applicationPaths; + _jellyfinDatabaseProvider = jellyfinDatabaseProvider; + _hostApplicationLifetime = applicationLifetime; + } + + /// <inheritdoc/> + public void ScheduleRestoreAndRestartServer(string archivePath) + { + _applicationHost.RestoreBackupPath = archivePath; + _applicationHost.ShouldRestart = true; + _applicationHost.NotifyPendingRestart(); + _ = Task.Run(async () => + { + await Task.Delay(500).ConfigureAwait(false); + _hostApplicationLifetime.StopApplication(); + }); + } + + /// <inheritdoc/> + public async Task RestoreBackupAsync(string archivePath) + { + _logger.LogWarning("Begin restoring system to {BackupArchive}", archivePath); // Info isn't cutting it + if (!File.Exists(archivePath)) + { + throw new FileNotFoundException($"Requested backup file '{archivePath}' does not exist."); + } + + StorageHelper.TestCommonPathsForStorageCapacity(_applicationPaths, _logger); + + var fileStream = File.OpenRead(archivePath); + await using (fileStream.ConfigureAwait(false)) + { + using var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Read, false); + var zipArchiveEntry = zipArchive.GetEntry(ManifestEntryName); + + if (zipArchiveEntry is null) + { + throw new NotSupportedException($"The loaded archive '{archivePath}' does not appear to be a Jellyfin backup as its missing the '{ManifestEntryName}'."); + } + + BackupManifest? manifest; + var manifestStream = await zipArchiveEntry.OpenAsync().ConfigureAwait(false); + await using (manifestStream.ConfigureAwait(false)) + { + manifest = await JsonSerializer.DeserializeAsync<BackupManifest>(manifestStream, _serializerSettings).ConfigureAwait(false); + } + + if (manifest!.ServerVersion > _applicationHost.ApplicationVersion) // newer versions of Jellyfin should be able to load older versions as we have migrations. + { + throw new NotSupportedException($"The loaded archive '{archivePath}' is made for a newer version of Jellyfin ({manifest.ServerVersion}) and cannot be loaded in this version."); + } + + if (!TestBackupVersionCompatibility(manifest.BackupEngineVersion)) + { + throw new NotSupportedException($"The loaded archive '{archivePath}' is made for a newer version of Jellyfin ({manifest.ServerVersion}) and cannot be loaded in this version."); + } + + void CopyDirectory(string source, string target) + { + var fullSourcePath = NormalizePathSeparator(Path.GetFullPath(source) + Path.DirectorySeparatorChar); + var fullTargetRoot = Path.GetFullPath(target) + Path.DirectorySeparatorChar; + foreach (var item in zipArchive.Entries) + { + var sourcePath = NormalizePathSeparator(Path.GetFullPath(item.FullName)); + var targetPath = Path.GetFullPath(Path.Combine(target, Path.GetRelativePath(source, item.FullName))); + + if (!sourcePath.StartsWith(fullSourcePath, StringComparison.Ordinal) + || !targetPath.StartsWith(fullTargetRoot, StringComparison.Ordinal) + || Path.EndsInDirectorySeparator(item.FullName)) + { + continue; + } + + _logger.LogInformation("Restore and override {File}", targetPath); + + Directory.CreateDirectory(Path.GetDirectoryName(targetPath)!); + item.ExtractToFile(targetPath, overwrite: true); + } + } + + CopyDirectory("Config", _applicationPaths.ConfigurationDirectoryPath); + CopyDirectory("Data", _applicationPaths.DataPath); + CopyDirectory("Root", _applicationPaths.RootFolderPath); + + if (manifest.Options.Database) + { + _logger.LogInformation("Begin restoring Database"); + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + // restore migration history manually + var historyEntry = zipArchive.GetEntry(NormalizePathSeparator(Path.Combine("Database", $"{nameof(HistoryRow)}.json"))); + if (historyEntry is null) + { + _logger.LogInformation("No backup of the history table in archive. This is required for Jellyfin operation"); + throw new InvalidOperationException("Cannot restore backup that has no History data."); + } + + HistoryRow[] historyEntries; + var historyArchive = await historyEntry.OpenAsync().ConfigureAwait(false); + await using (historyArchive.ConfigureAwait(false)) + { + historyEntries = await JsonSerializer.DeserializeAsync<HistoryRow[]>(historyArchive).ConfigureAwait(false) ?? + throw new InvalidOperationException("Cannot restore backup that has no History data."); + } + + var historyRepository = dbContext.GetService<IHistoryRepository>(); + await historyRepository.CreateIfNotExistsAsync().ConfigureAwait(false); + + foreach (var item in await historyRepository.GetAppliedMigrationsAsync(CancellationToken.None).ConfigureAwait(false)) + { + var insertScript = historyRepository.GetDeleteScript(item.MigrationId); + await dbContext.Database.ExecuteSqlRawAsync(insertScript).ConfigureAwait(false); + } + + foreach (var item in historyEntries) + { + var insertScript = historyRepository.GetInsertScript(item); + await dbContext.Database.ExecuteSqlRawAsync(insertScript).ConfigureAwait(false); + } + + dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + var entityTypes = typeof(JellyfinDbContext).GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance) + .Where(e => e.PropertyType.IsAssignableTo(typeof(IQueryable))) + .Select(e => (Type: e, Set: e.GetValue(dbContext) as IQueryable)) + .ToArray(); + + var tableNames = entityTypes.Select(f => dbContext.Model.FindEntityType(f.Type.PropertyType.GetGenericArguments()[0])!.GetSchemaQualifiedTableName()!); + _logger.LogInformation("Begin purging database"); + await _jellyfinDatabaseProvider.PurgeDatabase(dbContext, tableNames).ConfigureAwait(false); + _logger.LogInformation("Database Purged"); + + foreach (var entityType in entityTypes) + { + _logger.LogInformation("Read backup of {Table}", entityType.Type.Name); + + var zipEntry = zipArchive.GetEntry(NormalizePathSeparator(Path.Combine("Database", $"{entityType.Type.Name}.json"))); + if (zipEntry is null) + { + _logger.LogInformation("No backup of expected table {Table} is present in backup, continuing anyway", entityType.Type.Name); + continue; + } + + var zipEntryStream = await zipEntry.OpenAsync().ConfigureAwait(false); + await using (zipEntryStream.ConfigureAwait(false)) + { + _logger.LogInformation("Restore backup of {Table}", entityType.Type.Name); + var records = 0; + await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<JsonObject>(zipEntryStream, _serializerSettings).ConfigureAwait(false)) + { + var entity = item.Deserialize(entityType.Type.PropertyType.GetGenericArguments()[0]); + if (entity is null) + { + throw new InvalidOperationException($"Cannot deserialize entity '{item}'"); + } + + try + { + records++; + dbContext.Add(entity); + } + catch (Exception ex) + { + _logger.LogError(ex, "Could not store entity {Entity}, continuing anyway", item); + } + } + + _logger.LogInformation("Prepared to restore {Number} entries for {Table}", records, entityType.Type.Name); + } + } + + _logger.LogInformation("Try restore Database"); + await dbContext.SaveChangesAsync().ConfigureAwait(false); + _logger.LogInformation("Restored database"); + } + } + + _logger.LogInformation("Restored Jellyfin system from {Date}", manifest.DateCreated); + } + } + + private bool TestBackupVersionCompatibility(Version backupEngineVersion) + { + if (backupEngineVersion == _backupEngineVersion) + { + return true; + } + + return false; + } + + /// <inheritdoc/> + public async Task<BackupManifestDto> CreateBackupAsync(BackupOptionsDto backupOptions) + { + var manifest = new BackupManifest() + { + DateCreated = DateTime.UtcNow, + ServerVersion = _applicationHost.ApplicationVersion, + DatabaseTables = null!, + BackupEngineVersion = _backupEngineVersion, + Options = Map(backupOptions) + }; + + _logger.LogInformation("Running database optimization before backup"); + + await _jellyfinDatabaseProvider.RunScheduledOptimisation(CancellationToken.None).ConfigureAwait(false); + + var backupFolder = Path.Combine(_applicationPaths.BackupPath); + + if (!Directory.Exists(backupFolder)) + { + Directory.CreateDirectory(backupFolder); + } + + var backupStorageSpace = StorageHelper.GetFreeSpaceOf(_applicationPaths.BackupPath); + + const long FiveGigabyte = 5_368_709_115; + if (backupStorageSpace.FreeSpace < FiveGigabyte) + { + throw new InvalidOperationException($"The backup directory '{backupStorageSpace.Path}' does not have at least '{StorageHelper.HumanizeStorageSize(FiveGigabyte)}' free space. Cannot create backup."); + } + + var backupPath = Path.Combine(backupFolder, $"jellyfin-backup-{manifest.DateCreated.ToLocalTime():yyyyMMddHHmmss}.zip"); + + try + { + _logger.LogInformation("Attempting to create a new backup at {BackupPath}", backupPath); + var fileStream = File.OpenWrite(backupPath); + await using (fileStream.ConfigureAwait(false)) + using (var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Create, false)) + { + _logger.LogInformation("Starting backup process"); + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + + static IAsyncEnumerable<object> GetValues(IQueryable dbSet) + { + var method = dbSet.GetType().GetMethod(nameof(DbSet<object>.AsAsyncEnumerable))!; + var enumerable = method.Invoke(dbSet, null)!; + return (IAsyncEnumerable<object>)enumerable; + } + + // include the migration history as well + var historyRepository = dbContext.GetService<IHistoryRepository>(); + var migrations = await historyRepository.GetAppliedMigrationsAsync().ConfigureAwait(false); + + ICollection<(Type Type, string SourceName, Func<IAsyncEnumerable<object>> ValueFactory)> entityTypes = + [ + .. typeof(JellyfinDbContext) + .GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance) + .Where(e => e.PropertyType.IsAssignableTo(typeof(IQueryable))) + .Select(e => (Type: e.PropertyType, dbContext.Model.FindEntityType(e.PropertyType.GetGenericArguments()[0])!.GetSchemaQualifiedTableName()!, ValueFactory: new Func<IAsyncEnumerable<object>>(() => GetValues((IQueryable)e.GetValue(dbContext)!)))), + (Type: typeof(HistoryRow), SourceName: nameof(HistoryRow), ValueFactory: () => migrations.ToAsyncEnumerable()) + ]; + manifest.DatabaseTables = entityTypes.Select(e => e.Type.Name).ToArray(); + var transaction = await dbContext.Database.BeginTransactionAsync().ConfigureAwait(false); + + await using (transaction.ConfigureAwait(false)) + { + _logger.LogInformation("Begin Database backup"); + + foreach (var entityType in entityTypes) + { + _logger.LogInformation("Begin backup of entity {Table}", entityType.SourceName); + var zipEntry = zipArchive.CreateEntry(NormalizePathSeparator(Path.Combine("Database", $"{entityType.SourceName}.json"))); + var entities = 0; + var zipEntryStream = await zipEntry.OpenAsync().ConfigureAwait(false); + await using (zipEntryStream.ConfigureAwait(false)) + { + var jsonSerializer = new Utf8JsonWriter(zipEntryStream); + await using (jsonSerializer.ConfigureAwait(false)) + { + jsonSerializer.WriteStartArray(); + + var set = entityType.ValueFactory().ConfigureAwait(false); + await foreach (var item in set.ConfigureAwait(false)) + { + entities++; + try + { + using var document = JsonSerializer.SerializeToDocument(item, _serializerSettings); + document.WriteTo(jsonSerializer); + } + catch (Exception ex) + { + _logger.LogError(ex, "Could not load entity {Entity}", item); + throw; + } + } + + jsonSerializer.WriteEndArray(); + } + } + + _logger.LogInformation("Backup of entity {Table} with {Number} created", entityType.SourceName, entities); + } + } + } + + _logger.LogInformation("Backup of folder {Table}", _applicationPaths.ConfigurationDirectoryPath); + foreach (var item in Directory.EnumerateFiles(_applicationPaths.ConfigurationDirectoryPath, "*.xml", SearchOption.TopDirectoryOnly) + .Union(Directory.EnumerateFiles(_applicationPaths.ConfigurationDirectoryPath, "*.json", SearchOption.TopDirectoryOnly))) + { + await zipArchive.CreateEntryFromFileAsync(item, NormalizePathSeparator(Path.Combine("Config", Path.GetFileName(item)))).ConfigureAwait(false); + } + + void CopyDirectory(string source, string target, string filter = "*") + { + if (!Directory.Exists(source)) + { + return; + } + + _logger.LogInformation("Backup of folder {Table}", source); + + foreach (var item in Directory.EnumerateFiles(source, filter, SearchOption.AllDirectories)) + { + // TODO: @bond make async + zipArchive.CreateEntryFromFile(item, NormalizePathSeparator(Path.Combine(target, Path.GetRelativePath(source, item)))); + } + } + + CopyDirectory(Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "users"), Path.Combine("Config", "users")); + CopyDirectory(Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "ScheduledTasks"), Path.Combine("Config", "ScheduledTasks")); + CopyDirectory(Path.Combine(_applicationPaths.RootFolderPath), "Root"); + CopyDirectory(Path.Combine(_applicationPaths.DataPath, "collections"), Path.Combine("Data", "collections")); + CopyDirectory(Path.Combine(_applicationPaths.DataPath, "playlists"), Path.Combine("Data", "playlists")); + CopyDirectory(Path.Combine(_applicationPaths.DataPath, "ScheduledTasks"), Path.Combine("Data", "ScheduledTasks")); + if (backupOptions.Subtitles) + { + CopyDirectory(Path.Combine(_applicationPaths.DataPath, "subtitles"), Path.Combine("Data", "subtitles")); + } + + if (backupOptions.Trickplay) + { + CopyDirectory(Path.Combine(_applicationPaths.DataPath, "trickplay"), Path.Combine("Data", "trickplay")); + } + + if (backupOptions.Metadata) + { + CopyDirectory(Path.Combine(_applicationPaths.InternalMetadataPath), Path.Combine("Data", "metadata")); + } + + var manifestStream = await zipArchive.CreateEntry(ManifestEntryName).OpenAsync().ConfigureAwait(false); + await using (manifestStream.ConfigureAwait(false)) + { + await JsonSerializer.SerializeAsync(manifestStream, manifest).ConfigureAwait(false); + } + } + + _logger.LogInformation("Backup created"); + return Map(manifest, backupPath); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to create backup, removing {BackupPath}", backupPath); + try + { + if (File.Exists(backupPath)) + { + File.Delete(backupPath); + } + } + catch (Exception innerEx) + { + _logger.LogWarning(innerEx, "Unable to remove failed backup"); + } + + throw; + } + } + + /// <inheritdoc/> + public async Task<BackupManifestDto?> GetBackupManifest(string archivePath) + { + if (!File.Exists(archivePath)) + { + return null; + } + + BackupManifest? manifest; + try + { + manifest = await GetManifest(archivePath).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Tried to load manifest from archive {Path} but failed", archivePath); + return null; + } + + if (manifest is null) + { + return null; + } + + return Map(manifest, archivePath); + } + + /// <inheritdoc/> + public async Task<BackupManifestDto[]> EnumerateBackups() + { + if (!Directory.Exists(_applicationPaths.BackupPath)) + { + return []; + } + + var archives = Directory.EnumerateFiles(_applicationPaths.BackupPath, "*.zip"); + var manifests = new List<BackupManifestDto>(); + foreach (var item in archives) + { + try + { + var manifest = await GetManifest(item).ConfigureAwait(false); + + if (manifest is null) + { + continue; + } + + manifests.Add(Map(manifest, item)); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Tried to load manifest from archive {Path} but failed", item); + } + } + + return manifests.ToArray(); + } + + private static async ValueTask<BackupManifest?> GetManifest(string archivePath) + { + var archiveStream = File.OpenRead(archivePath); + await using (archiveStream.ConfigureAwait(false)) + { + using var zipStream = new ZipArchive(archiveStream, ZipArchiveMode.Read); + var manifestEntry = zipStream.GetEntry(ManifestEntryName); + if (manifestEntry is null) + { + return null; + } + + var manifestStream = await manifestEntry.OpenAsync().ConfigureAwait(false); + await using (manifestStream.ConfigureAwait(false)) + { + return await JsonSerializer.DeserializeAsync<BackupManifest>(manifestStream, _serializerSettings).ConfigureAwait(false); + } + } + } + + private static BackupManifestDto Map(BackupManifest manifest, string path) + { + return new BackupManifestDto() + { + BackupEngineVersion = manifest.BackupEngineVersion, + DateCreated = manifest.DateCreated, + ServerVersion = manifest.ServerVersion, + Path = path, + Options = Map(manifest.Options) + }; + } + + private static BackupOptionsDto Map(BackupOptions options) + { + return new BackupOptionsDto() + { + Metadata = options.Metadata, + Subtitles = options.Subtitles, + Trickplay = options.Trickplay, + Database = options.Database + }; + } + + private static BackupOptions Map(BackupOptionsDto options) + { + return new BackupOptions() + { + Metadata = options.Metadata, + Subtitles = options.Subtitles, + Trickplay = options.Trickplay, + Database = options.Database + }; + } + + /// <summary> + /// Windows is able to handle '/' as a path seperator in zip files + /// but linux isn't able to handle '\' as a path seperator in zip files, + /// So normalize to '/'. + /// </summary> + /// <param name="path">The path to normalize.</param> + /// <returns>The normalized path. </returns> + private static string NormalizePathSeparator(string path) + => path.Replace('\\', '/'); +} diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs new file mode 100644 index 00000000..5bb4494d --- /dev/null +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -0,0 +1,2735 @@ +#pragma warning disable RS0030 // Do not use banned APIs +// Do not enforce that because EFCore cannot deal with cultures well. +#pragma warning disable CA1304 // Specify CultureInfo +#pragma warning disable CA1311 // Specify a culture or use an invariant version +#pragma warning disable CA1862 // Use the 'StringComparison' method overloads to perform case-insensitive string comparisons + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using Jellyfin.Extensions.Json; +using Jellyfin.Server.Implementations.Extensions; +using MediaBrowser.Common; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.LiveTv; +using MediaBrowser.Model.Querying; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem; +using BaseItemEntity = Jellyfin.Database.Implementations.Entities.BaseItemEntity; + +namespace Jellyfin.Server.Implementations.Item; + +/* + All queries in this class and all other nullable enabled EFCore repository classes will make liberal use of the null-forgiving operator "!". + This is done as the code isn't actually executed client side, but only the expressions are interpret and the compiler cannot know that. + This is your only warning/message regarding this topic. +*/ + +/// <summary> +/// Handles all storage logic for BaseItems. +/// </summary> +public sealed class BaseItemRepository + : IItemRepository +{ + /// <summary> + /// Gets the placeholder id for UserData detached items. + /// </summary> + public static readonly Guid PlaceholderId = Guid.Parse("00000000-0000-0000-0000-000000000001"); + + /// <summary> + /// This holds all the types in the running assemblies + /// so that we can de-serialize properly when we don't have strong types. + /// </summary> + private static readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>(); + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + private readonly IServerApplicationHost _appHost; + private readonly IItemTypeLookup _itemTypeLookup; + private readonly IServerConfigurationManager _serverConfigurationManager; + private readonly ILogger<BaseItemRepository> _logger; + + private static readonly IReadOnlyList<ItemValueType> _getAllArtistsValueTypes = [ItemValueType.Artist, ItemValueType.AlbumArtist]; + private static readonly IReadOnlyList<ItemValueType> _getArtistValueTypes = [ItemValueType.Artist]; + private static readonly IReadOnlyList<ItemValueType> _getAlbumArtistValueTypes = [ItemValueType.AlbumArtist]; + private static readonly IReadOnlyList<ItemValueType> _getStudiosValueTypes = [ItemValueType.Studios]; + private static readonly IReadOnlyList<ItemValueType> _getGenreValueTypes = [ItemValueType.Genre]; + private static readonly IReadOnlyList<char> SearchWildcardTerms = ['%', '_', '[', ']', '^']; + + /// <summary> + /// Initializes a new instance of the <see cref="BaseItemRepository"/> class. + /// </summary> + /// <param name="dbProvider">The db factory.</param> + /// <param name="appHost">The Application host.</param> + /// <param name="itemTypeLookup">The static type lookup.</param> + /// <param name="serverConfigurationManager">The server Configuration manager.</param> + /// <param name="logger">System logger.</param> + public BaseItemRepository( + IDbContextFactory<JellyfinDbContext> dbProvider, + IServerApplicationHost appHost, + IItemTypeLookup itemTypeLookup, + IServerConfigurationManager serverConfigurationManager, + ILogger<BaseItemRepository> logger) + { + _dbProvider = dbProvider; + _appHost = appHost; + _itemTypeLookup = itemTypeLookup; + _serverConfigurationManager = serverConfigurationManager; + _logger = logger; + } + + /// <inheritdoc /> + public void DeleteItem(params IReadOnlyList<Guid> ids) + { + if (ids is null || ids.Count == 0 || ids.Any(f => f.Equals(PlaceholderId))) + { + throw new ArgumentException("Guid can't be empty or the placeholder id.", nameof(ids)); + } + + using var context = _dbProvider.CreateDbContext(); + using var transaction = context.Database.BeginTransaction(); + + var date = (DateTime?)DateTime.UtcNow; + + var relatedItems = ids.SelectMany(f => TraverseHirachyDown(f, context)).ToArray(); + + // Remove any UserData entries for the placeholder item that would conflict with the UserData + // being detached from the item being deleted. This is necessary because, during an update, + // UserData may be reattached to a new entry, but some entries can be left behind. + // Ensures there are no duplicate UserId/CustomDataKey combinations for the placeholder. + context.UserData + .Join( + context.UserData.WhereOneOrMany(relatedItems, e => e.ItemId), + placeholder => new { placeholder.UserId, placeholder.CustomDataKey }, + userData => new { userData.UserId, userData.CustomDataKey }, + (placeholder, userData) => placeholder) + .Where(e => e.ItemId == PlaceholderId) + .ExecuteDelete(); + + // Detach all user watch data + context.UserData.WhereOneOrMany(relatedItems, e => e.ItemId) + .ExecuteUpdate(e => e + .SetProperty(f => f.RetentionDate, date) + .SetProperty(f => f.ItemId, PlaceholderId)); + + context.AncestorIds.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.AncestorIds.WhereOneOrMany(relatedItems, e => e.ParentItemId).ExecuteDelete(); + context.AttachmentStreamInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.BaseItemImageInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.BaseItemMetadataFields.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.BaseItemProviders.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.BaseItemTrailerTypes.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.BaseItems.WhereOneOrMany(relatedItems, e => e.Id).ExecuteDelete(); + context.Chapters.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.CustomItemDisplayPreferences.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.ItemDisplayPreferences.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDelete(); + context.ItemValuesMap.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.KeyframeData.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.MediaSegments.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.MediaStreamInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + var query = context.PeopleBaseItemMap.WhereOneOrMany(relatedItems, e => e.ItemId).Select(f => f.PeopleId).Distinct().ToArray(); + context.PeopleBaseItemMap.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.Peoples.WhereOneOrMany(query, e => e.Id).Where(e => e.BaseItems!.Count == 0).ExecuteDelete(); + context.TrickplayInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.SaveChanges(); + transaction.Commit(); + } + + /// <inheritdoc /> + public void UpdateInheritedValues() + { + using var context = _dbProvider.CreateDbContext(); + using var transaction = context.Database.BeginTransaction(); + + context.ItemValuesMap.Where(e => e.ItemValue.Type == ItemValueType.InheritedTags).ExecuteDelete(); + // ItemValue Inheritance is now correctly mapped via AncestorId on demand + context.SaveChanges(); + + transaction.Commit(); + } + + /// <inheritdoc /> + public IReadOnlyList<Guid> GetItemIdsList(InternalItemsQuery filter) + { + ArgumentNullException.ThrowIfNull(filter); + PrepareFilterQuery(filter); + + using var context = _dbProvider.CreateDbContext(); + return ApplyQueryFilter(context.BaseItems.AsNoTracking().Where(e => e.Id != EF.Constant(PlaceholderId)), context, filter).Select(e => e.Id).ToArray(); + } + + /// <inheritdoc /> + public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetAllArtists(InternalItemsQuery filter) + { + return GetItemValues(filter, _getAllArtistsValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]); + } + + /// <inheritdoc /> + public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetArtists(InternalItemsQuery filter) + { + return GetItemValues(filter, _getArtistValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]); + } + + /// <inheritdoc /> + public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetAlbumArtists(InternalItemsQuery filter) + { + return GetItemValues(filter, _getAlbumArtistValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]); + } + + /// <inheritdoc /> + public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetStudios(InternalItemsQuery filter) + { + return GetItemValues(filter, _getStudiosValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.Studio]); + } + + /// <inheritdoc /> + public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetGenres(InternalItemsQuery filter) + { + return GetItemValues(filter, _getGenreValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.Genre]); + } + + /// <inheritdoc /> + public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetMusicGenres(InternalItemsQuery filter) + { + return GetItemValues(filter, _getGenreValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicGenre]); + } + + /// <inheritdoc /> + public IReadOnlyList<string> GetStudioNames() + { + return GetItemValueNames(_getStudiosValueTypes, [], []); + } + + /// <inheritdoc /> + public IReadOnlyList<string> GetAllArtistNames() + { + return GetItemValueNames(_getAllArtistsValueTypes, [], []); + } + + /// <inheritdoc /> + public IReadOnlyList<string> GetMusicGenreNames() + { + return GetItemValueNames( + _getGenreValueTypes, + _itemTypeLookup.MusicGenreTypes, + []); + } + + /// <inheritdoc /> + public IReadOnlyList<string> GetGenreNames() + { + return GetItemValueNames( + _getGenreValueTypes, + [], + _itemTypeLookup.MusicGenreTypes); + } + + /// <inheritdoc /> + public QueryResult<BaseItemDto> GetItems(InternalItemsQuery filter) + { + ArgumentNullException.ThrowIfNull(filter); + if (!filter.EnableTotalRecordCount || ((filter.Limit ?? 0) == 0 && (filter.StartIndex ?? 0) == 0)) + { + var returnList = GetItemList(filter); + return new QueryResult<BaseItemDto>( + filter.StartIndex, + returnList.Count, + returnList); + } + + PrepareFilterQuery(filter); + var result = new QueryResult<BaseItemDto>(); + + using var context = _dbProvider.CreateDbContext(); + + IQueryable<BaseItemEntity> dbQuery = PrepareItemQuery(context, filter); + + dbQuery = TranslateQuery(dbQuery, context, filter); + dbQuery = ApplyGroupingFilter(context, dbQuery, filter); + + if (filter.EnableTotalRecordCount) + { + result.TotalRecordCount = dbQuery.Count(); + } + + dbQuery = ApplyQueryPaging(dbQuery, filter); + dbQuery = ApplyNavigations(dbQuery, filter); + + result.Items = dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).Where(dto => dto is not null).ToArray()!; + result.StartIndex = filter.StartIndex ?? 0; + return result; + } + + /// <inheritdoc /> + public IReadOnlyList<BaseItemDto> GetItemList(InternalItemsQuery filter) + { + ArgumentNullException.ThrowIfNull(filter); + PrepareFilterQuery(filter); + + using var context = _dbProvider.CreateDbContext(); + IQueryable<BaseItemEntity> dbQuery = PrepareItemQuery(context, filter); + + dbQuery = TranslateQuery(dbQuery, context, filter); + + dbQuery = ApplyGroupingFilter(context, dbQuery, filter); + dbQuery = ApplyQueryPaging(dbQuery, filter); + + var hasRandomSort = filter.OrderBy.Any(e => e.OrderBy == ItemSortBy.Random); + if (hasRandomSort) + { + var orderedIds = dbQuery.Select(e => e.Id).ToList(); + if (orderedIds.Count == 0) + { + return Array.Empty<BaseItemDto>(); + } + + var itemsById = ApplyNavigations(context.BaseItems.Where(e => orderedIds.Contains(e.Id)), filter) + .AsEnumerable() + .Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)) + .Where(dto => dto is not null) + .ToDictionary(i => i!.Id); + + return orderedIds.Where(itemsById.ContainsKey).Select(id => itemsById[id]).ToArray()!; + } + + dbQuery = ApplyNavigations(dbQuery, filter); + + return dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).Where(dto => dto is not null).ToArray()!; + } + + /// <inheritdoc/> + public IReadOnlyList<BaseItem> GetLatestItemList(InternalItemsQuery filter, CollectionType collectionType) + { + ArgumentNullException.ThrowIfNull(filter); + PrepareFilterQuery(filter); + + // Early exit if collection type is not tvshows or music + if (collectionType != CollectionType.tvshows && collectionType != CollectionType.music) + { + return Array.Empty<BaseItem>(); + } + + using var context = _dbProvider.CreateDbContext(); + + // Subquery to group by SeriesNames/Album and get the max Date Created for each group. + var subquery = PrepareItemQuery(context, filter); + subquery = TranslateQuery(subquery, context, filter); + var subqueryGrouped = subquery.GroupBy(g => collectionType == CollectionType.tvshows ? g.SeriesName : g.Album) + .Select(g => new + { + Key = g.Key, + MaxDateCreated = g.Max(a => a.DateCreated) + }) + .OrderByDescending(g => g.MaxDateCreated) + .Select(g => g); + + if (filter.Limit.HasValue && filter.Limit.Value > 0) + { + subqueryGrouped = subqueryGrouped.Take(filter.Limit.Value); + } + + filter.Limit = null; + + var mainquery = PrepareItemQuery(context, filter); + mainquery = TranslateQuery(mainquery, context, filter); + mainquery = mainquery.Where(g => g.DateCreated >= subqueryGrouped.Min(s => s.MaxDateCreated)); + mainquery = ApplyGroupingFilter(context, mainquery, filter); + mainquery = ApplyQueryPaging(mainquery, filter); + + mainquery = ApplyNavigations(mainquery, filter); + + return mainquery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).Where(dto => dto is not null).ToArray()!; + } + + /// <inheritdoc /> + public IReadOnlyList<string> GetNextUpSeriesKeys(InternalItemsQuery filter, DateTime dateCutoff) + { + ArgumentNullException.ThrowIfNull(filter); + ArgumentNullException.ThrowIfNull(filter.User); + + using var context = _dbProvider.CreateDbContext(); + + var query = context.BaseItems + .AsNoTracking() + .Where(i => filter.TopParentIds.Contains(i.TopParentId!.Value)) + .Where(i => i.Type == _itemTypeLookup.BaseItemKindNames[BaseItemKind.Episode]) + .Join( + context.UserData.AsNoTracking().Where(e => e.ItemId != EF.Constant(PlaceholderId)), + i => new { UserId = filter.User.Id, ItemId = i.Id }, + u => new { UserId = u.UserId, ItemId = u.ItemId }, + (entity, data) => new { Item = entity, UserData = data }) + .GroupBy(g => g.Item.SeriesPresentationUniqueKey) + .Select(g => new { g.Key, LastPlayedDate = g.Max(u => u.UserData.LastPlayedDate) }) + .Where(g => g.Key != null && g.LastPlayedDate != null && g.LastPlayedDate >= dateCutoff) + .OrderByDescending(g => g.LastPlayedDate) + .Select(g => g.Key!); + + if (filter.Limit.HasValue && filter.Limit.Value > 0) + { + query = query.Take(filter.Limit.Value); + } + + return query.ToArray(); + } + + private IQueryable<BaseItemEntity> ApplyGroupingFilter(JellyfinDbContext context, IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter) + { + // This whole block is needed to filter duplicate entries on request + // for the time being it cannot be used because it would destroy the ordering + // this results in "duplicate" responses for queries that try to lookup individual series or multiple versions but + // for that case the invoker has to run a DistinctBy(e => e.PresentationUniqueKey) on their own + + var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(filter); + if (enableGroupByPresentationUniqueKey && filter.GroupBySeriesPresentationUniqueKey) + { + var tempQuery = dbQuery.GroupBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey }).Select(e => e.FirstOrDefault()).Select(e => e!.Id); + dbQuery = context.BaseItems.Where(e => tempQuery.Contains(e.Id)); + } + else if (enableGroupByPresentationUniqueKey) + { + var tempQuery = dbQuery.GroupBy(e => e.PresentationUniqueKey).Select(e => e.FirstOrDefault()).Select(e => e!.Id); + dbQuery = context.BaseItems.Where(e => tempQuery.Contains(e.Id)); + } + else if (filter.GroupBySeriesPresentationUniqueKey) + { + var tempQuery = dbQuery.GroupBy(e => e.SeriesPresentationUniqueKey).Select(e => e.FirstOrDefault()).Select(e => e!.Id); + dbQuery = context.BaseItems.Where(e => tempQuery.Contains(e.Id)); + } + else + { + dbQuery = dbQuery.Distinct(); + } + + dbQuery = ApplyOrder(dbQuery, filter, context); + + return dbQuery; + } + + private static IQueryable<BaseItemEntity> ApplyNavigations(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter) + { + if (filter.TrailerTypes.Length > 0 || filter.IncludeItemTypes.Contains(BaseItemKind.Trailer)) + { + dbQuery = dbQuery.Include(e => e.TrailerTypes); + } + + if (filter.DtoOptions.ContainsField(ItemFields.ProviderIds)) + { + dbQuery = dbQuery.Include(e => e.Provider); + } + + if (filter.DtoOptions.ContainsField(ItemFields.Settings)) + { + dbQuery = dbQuery.Include(e => e.LockedFields); + } + + if (filter.DtoOptions.EnableUserData) + { + dbQuery = dbQuery.Include(e => e.UserData); + } + + if (filter.DtoOptions.EnableImages) + { + dbQuery = dbQuery.Include(e => e.Images); + } + + return dbQuery; + } + + private IQueryable<BaseItemEntity> ApplyQueryPaging(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter) + { + if (filter.StartIndex.HasValue && filter.StartIndex.Value > 0) + { + dbQuery = dbQuery.Skip(filter.StartIndex.Value); + } + + if (filter.Limit.HasValue && filter.Limit.Value > 0) + { + dbQuery = dbQuery.Take(filter.Limit.Value); + } + + return dbQuery; + } + + private IQueryable<BaseItemEntity> ApplyQueryFilter(IQueryable<BaseItemEntity> dbQuery, JellyfinDbContext context, InternalItemsQuery filter) + { + dbQuery = TranslateQuery(dbQuery, context, filter); + dbQuery = ApplyGroupingFilter(context, dbQuery, filter); + dbQuery = ApplyQueryPaging(dbQuery, filter); + dbQuery = ApplyNavigations(dbQuery, filter); + return dbQuery; + } + + private IQueryable<BaseItemEntity> PrepareItemQuery(JellyfinDbContext context, InternalItemsQuery filter) + { + IQueryable<BaseItemEntity> dbQuery = context.BaseItems.AsNoTracking(); + dbQuery = dbQuery.AsSingleQuery(); + + return dbQuery; + } + + /// <inheritdoc/> + public int GetCount(InternalItemsQuery filter) + { + ArgumentNullException.ThrowIfNull(filter); + // Hack for right now since we currently don't support filtering out these duplicates within a query + PrepareFilterQuery(filter); + + using var context = _dbProvider.CreateDbContext(); + var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter); + + return dbQuery.Count(); + } + + /// <inheritdoc /> + public ItemCounts GetItemCounts(InternalItemsQuery filter) + { + ArgumentNullException.ThrowIfNull(filter); + // Hack for right now since we currently don't support filtering out these duplicates within a query + PrepareFilterQuery(filter); + + using var context = _dbProvider.CreateDbContext(); + var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter); + + var counts = dbQuery + .GroupBy(x => x.Type) + .Select(x => new { x.Key, Count = x.Count() }) + .ToArray(); + + var lookup = _itemTypeLookup.BaseItemKindNames; + var result = new ItemCounts(); + foreach (var count in counts) + { + if (string.Equals(count.Key, lookup[BaseItemKind.MusicAlbum], StringComparison.Ordinal)) + { + result.AlbumCount = count.Count; + } + else if (string.Equals(count.Key, lookup[BaseItemKind.MusicArtist], StringComparison.Ordinal)) + { + result.ArtistCount = count.Count; + } + else if (string.Equals(count.Key, lookup[BaseItemKind.Episode], StringComparison.Ordinal)) + { + result.EpisodeCount = count.Count; + } + else if (string.Equals(count.Key, lookup[BaseItemKind.Movie], StringComparison.Ordinal)) + { + result.MovieCount = count.Count; + } + else if (string.Equals(count.Key, lookup[BaseItemKind.MusicVideo], StringComparison.Ordinal)) + { + result.MusicVideoCount = count.Count; + } + else if (string.Equals(count.Key, lookup[BaseItemKind.LiveTvProgram], StringComparison.Ordinal)) + { + result.ProgramCount = count.Count; + } + else if (string.Equals(count.Key, lookup[BaseItemKind.Series], StringComparison.Ordinal)) + { + result.SeriesCount = count.Count; + } + else if (string.Equals(count.Key, lookup[BaseItemKind.Audio], StringComparison.Ordinal)) + { + result.SongCount = count.Count; + } + else if (string.Equals(count.Key, lookup[BaseItemKind.Trailer], StringComparison.Ordinal)) + { + result.TrailerCount = count.Count; + } + } + + return result; + } + +#pragma warning disable CA1307 // Specify StringComparison for clarity + /// <summary> + /// Gets the type. + /// </summary> + /// <param name="typeName">Name of the type.</param> + /// <returns>Type.</returns> + /// <exception cref="ArgumentNullException"><c>typeName</c> is null.</exception> + private static Type? GetType(string typeName) + { + ArgumentException.ThrowIfNullOrEmpty(typeName); + + // TODO: this isn't great. Refactor later to be both globally handled by a dedicated service not just an static variable and be loaded eagerly. + // currently this is done so that plugins may introduce their own type of baseitems as we dont know when we are first called, before or after plugins are loaded + return _typeMap.GetOrAdd(typeName, k => AppDomain.CurrentDomain.GetAssemblies() + .Select(a => a.GetType(k)) + .FirstOrDefault(t => t is not null)); + } + + /// <inheritdoc /> + public async Task SaveImagesAsync(BaseItemDto item, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(item); + + var images = item.ImageInfos.Select(e => Map(item.Id, e)).ToArray(); + + var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + await using (context.ConfigureAwait(false)) + { + if (!await context.BaseItems + .AnyAsync(bi => bi.Id == item.Id, cancellationToken) + .ConfigureAwait(false)) + { + _logger.LogWarning("Unable to save ImageInfo for non existing BaseItem"); + return; + } + + await context.BaseItemImageInfos + .Where(e => e.ItemId == item.Id) + .ExecuteDeleteAsync(cancellationToken) + .ConfigureAwait(false); + + await context.BaseItemImageInfos + .AddRangeAsync(images, cancellationToken) + .ConfigureAwait(false); + + await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + } + } + + /// <inheritdoc /> + public void SaveItems(IReadOnlyList<BaseItemDto> items, CancellationToken cancellationToken) + { + UpdateOrInsertItems(items, cancellationToken); + } + + /// <inheritdoc cref="IItemRepository"/> + public void UpdateOrInsertItems(IReadOnlyList<BaseItemDto> items, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(items); + cancellationToken.ThrowIfCancellationRequested(); + + var tuples = new List<(BaseItemDto Item, List<Guid>? AncestorIds, BaseItemDto TopParent, IEnumerable<string> UserDataKey, List<string> InheritedTags)>(); + foreach (var item in items.GroupBy(e => e.Id).Select(e => e.Last()).Where(e => e.Id != PlaceholderId)) + { + var ancestorIds = item.SupportsAncestors ? + item.GetAncestorIds().Distinct().ToList() : + null; + + var topParent = item.GetTopParent(); + + var userdataKey = item.GetUserDataKeys(); + var inheritedTags = item.GetInheritedTags(); + + tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags)); + } + + using var context = _dbProvider.CreateDbContext(); + using var transaction = context.Database.BeginTransaction(); + + var ids = tuples.Select(f => f.Item.Id).ToArray(); + var existingItems = context.BaseItems.Where(e => ids.Contains(e.Id)).Select(f => f.Id).ToArray(); + + foreach (var item in tuples) + { + var entity = Map(item.Item); + // TODO: refactor this "inconsistency" + entity.TopParentId = item.TopParent?.Id; + + if (!existingItems.Any(e => e == entity.Id)) + { + context.BaseItems.Add(entity); + } + else + { + context.BaseItemProviders.Where(e => e.ItemId == entity.Id).ExecuteDelete(); + context.BaseItemImageInfos.Where(e => e.ItemId == entity.Id).ExecuteDelete(); + context.BaseItemMetadataFields.Where(e => e.ItemId == entity.Id).ExecuteDelete(); + + if (entity.Images is { Count: > 0 }) + { + context.BaseItemImageInfos.AddRange(entity.Images); + } + + if (entity.LockedFields is { Count: > 0 }) + { + context.BaseItemMetadataFields.AddRange(entity.LockedFields); + } + + context.BaseItems.Attach(entity).State = EntityState.Modified; + } + } + + context.SaveChanges(); + + var itemValueMaps = tuples + .Select(e => (e.Item, Values: GetItemValuesToSave(e.Item, e.InheritedTags))) + .ToArray(); + var allListedItemValues = itemValueMaps + .SelectMany(f => f.Values) + .Distinct() + .ToArray(); + var existingValues = context.ItemValues + .Select(e => new + { + item = e, + Key = e.Type + "+" + e.Value + }) + .Where(f => allListedItemValues.Select(e => $"{(int)e.MagicNumber}+{e.Value}").Contains(f.Key)) + .Select(e => e.item) + .ToArray(); + var missingItemValues = allListedItemValues.Except(existingValues.Select(f => (MagicNumber: f.Type, f.Value))).Select(f => new ItemValue() + { + CleanValue = GetCleanValue(f.Value), + ItemValueId = Guid.NewGuid(), + Type = f.MagicNumber, + Value = f.Value + }).ToArray(); + context.ItemValues.AddRange(missingItemValues); + context.SaveChanges(); + + var itemValuesStore = existingValues.Concat(missingItemValues).ToArray(); + var valueMap = itemValueMaps + .Select(f => (f.Item, Values: f.Values.Select(e => itemValuesStore.First(g => g.Value == e.Value && g.Type == e.MagicNumber)).DistinctBy(e => e.ItemValueId).ToArray())) + .ToArray(); + + var mappedValues = context.ItemValuesMap.Where(e => ids.Contains(e.ItemId)).ToList(); + + foreach (var item in valueMap) + { + var itemMappedValues = mappedValues.Where(e => e.ItemId == item.Item.Id).ToList(); + foreach (var itemValue in item.Values) + { + var existingItem = itemMappedValues.FirstOrDefault(f => f.ItemValueId == itemValue.ItemValueId); + if (existingItem is null) + { + context.ItemValuesMap.Add(new ItemValueMap() + { + Item = null!, + ItemId = item.Item.Id, + ItemValue = null!, + ItemValueId = itemValue.ItemValueId + }); + } + else + { + // map exists, remove from list so its been handled. + itemMappedValues.Remove(existingItem); + } + } + + // all still listed values are not in the new list so remove them. + context.ItemValuesMap.RemoveRange(itemMappedValues); + } + + context.SaveChanges(); + + foreach (var item in tuples) + { + if (item.Item.SupportsAncestors && item.AncestorIds != null) + { + var existingAncestorIds = context.AncestorIds.Where(e => e.ItemId == item.Item.Id).ToList(); + var validAncestorIds = context.BaseItems.Where(e => item.AncestorIds.Contains(e.Id)).Select(f => f.Id).ToArray(); + foreach (var ancestorId in validAncestorIds) + { + var existingAncestorId = existingAncestorIds.FirstOrDefault(e => e.ParentItemId == ancestorId); + if (existingAncestorId is null) + { + context.AncestorIds.Add(new AncestorId() + { + ParentItemId = ancestorId, + ItemId = item.Item.Id, + Item = null!, + ParentItem = null! + }); + } + else + { + existingAncestorIds.Remove(existingAncestorId); + } + } + + context.AncestorIds.RemoveRange(existingAncestorIds); + } + } + + context.SaveChanges(); + transaction.Commit(); + } + + /// <inheritdoc /> + public async Task ReattachUserDataAsync(BaseItemDto item, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(item); + cancellationToken.ThrowIfCancellationRequested(); + + var dbContext = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + + await using (dbContext.ConfigureAwait(false)) + { + var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false); + await using (transaction.ConfigureAwait(false)) + { + var userKeys = item.GetUserDataKeys().ToArray(); + var retentionDate = (DateTime?)null; + + await dbContext.UserData + .Where(e => e.ItemId == PlaceholderId) + .Where(e => userKeys.Contains(e.CustomDataKey)) + .ExecuteUpdateAsync( + e => e + .SetProperty(f => f.ItemId, item.Id) + .SetProperty(f => f.RetentionDate, retentionDate), + cancellationToken).ConfigureAwait(false); + + // Rehydrate the cached userdata + item.UserData = await dbContext.UserData + .AsNoTracking() + .Where(e => e.ItemId == item.Id) + .ToArrayAsync(cancellationToken) + .ConfigureAwait(false); + + await transaction.CommitAsync(cancellationToken).ConfigureAwait(false); + } + } + } + + /// <inheritdoc /> + public BaseItemDto? RetrieveItem(Guid id) + { + if (id.IsEmpty()) + { + throw new ArgumentException("Guid can't be empty", nameof(id)); + } + + using var context = _dbProvider.CreateDbContext(); + var dbQuery = PrepareItemQuery(context, new() + { + DtoOptions = new() + { + EnableImages = true + } + }); + dbQuery = dbQuery.Include(e => e.TrailerTypes) + .Include(e => e.Provider) + .Include(e => e.LockedFields) + .Include(e => e.UserData) + .Include(e => e.Images); + + var item = dbQuery.FirstOrDefault(e => e.Id == id); + if (item is null) + { + return null; + } + + return DeserializeBaseItem(item); + } + + /// <summary> + /// Maps a Entity to the DTO. + /// </summary> + /// <param name="entity">The entity.</param> + /// <param name="dto">The dto base instance.</param> + /// <param name="appHost">The Application server Host.</param> + /// <param name="logger">The applogger.</param> + /// <returns>The dto to map.</returns> + public static BaseItemDto Map(BaseItemEntity entity, BaseItemDto dto, IServerApplicationHost? appHost, ILogger logger) + { + dto.Id = entity.Id; + dto.ParentId = entity.ParentId.GetValueOrDefault(); + dto.Path = appHost?.ExpandVirtualPath(entity.Path) ?? entity.Path; + dto.EndDate = entity.EndDate; + dto.CommunityRating = entity.CommunityRating; + dto.CustomRating = entity.CustomRating; + dto.IndexNumber = entity.IndexNumber; + dto.IsLocked = entity.IsLocked; + dto.Name = entity.Name; + dto.OfficialRating = entity.OfficialRating; + dto.Overview = entity.Overview; + dto.ParentIndexNumber = entity.ParentIndexNumber; + dto.PremiereDate = entity.PremiereDate; + dto.ProductionYear = entity.ProductionYear; + dto.SortName = entity.SortName; + dto.ForcedSortName = entity.ForcedSortName; + dto.RunTimeTicks = entity.RunTimeTicks; + dto.PreferredMetadataLanguage = entity.PreferredMetadataLanguage; + dto.PreferredMetadataCountryCode = entity.PreferredMetadataCountryCode; + dto.IsInMixedFolder = entity.IsInMixedFolder; + dto.InheritedParentalRatingValue = entity.InheritedParentalRatingValue; + dto.InheritedParentalRatingSubValue = entity.InheritedParentalRatingSubValue; + dto.CriticRating = entity.CriticRating; + dto.PresentationUniqueKey = entity.PresentationUniqueKey; + dto.OriginalTitle = entity.OriginalTitle; + dto.Album = entity.Album; + dto.LUFS = entity.LUFS; + dto.NormalizationGain = entity.NormalizationGain; + dto.IsVirtualItem = entity.IsVirtualItem; + dto.ExternalSeriesId = entity.ExternalSeriesId; + dto.Tagline = entity.Tagline; + dto.TotalBitrate = entity.TotalBitrate; + dto.ExternalId = entity.ExternalId; + dto.Size = entity.Size; + dto.Genres = string.IsNullOrWhiteSpace(entity.Genres) ? [] : entity.Genres.Split('|'); + dto.DateCreated = entity.DateCreated ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); + dto.DateModified = entity.DateModified ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); + dto.ChannelId = entity.ChannelId ?? Guid.Empty; + dto.DateLastRefreshed = entity.DateLastRefreshed ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); + dto.DateLastSaved = entity.DateLastSaved ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); + dto.OwnerId = string.IsNullOrWhiteSpace(entity.OwnerId) ? Guid.Empty : (Guid.TryParse(entity.OwnerId, out var ownerId) ? ownerId : Guid.Empty); + dto.Width = entity.Width.GetValueOrDefault(); + dto.Height = entity.Height.GetValueOrDefault(); + dto.UserData = entity.UserData; + + if (entity.Provider is not null) + { + dto.ProviderIds = entity.Provider.ToDictionary(e => e.ProviderId, e => e.ProviderValue); + } + + if (entity.ExtraType is not null) + { + dto.ExtraType = (ExtraType)entity.ExtraType; + } + + if (entity.LockedFields is not null) + { + dto.LockedFields = entity.LockedFields?.Select(e => (MetadataField)e.Id).ToArray() ?? []; + } + + if (entity.Audio is not null) + { + dto.Audio = (ProgramAudio)entity.Audio; + } + + dto.ExtraIds = string.IsNullOrWhiteSpace(entity.ExtraIds) ? [] : entity.ExtraIds.Split('|').Select(e => Guid.Parse(e)).ToArray(); + dto.ProductionLocations = entity.ProductionLocations?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? []; + dto.Studios = entity.Studios?.Split('|') ?? []; + dto.Tags = string.IsNullOrWhiteSpace(entity.Tags) ? [] : entity.Tags.Split('|'); + + if (dto is IHasProgramAttributes hasProgramAttributes) + { + hasProgramAttributes.IsMovie = entity.IsMovie; + hasProgramAttributes.IsSeries = entity.IsSeries; + hasProgramAttributes.EpisodeTitle = entity.EpisodeTitle; + hasProgramAttributes.IsRepeat = entity.IsRepeat; + } + + if (dto is LiveTvChannel liveTvChannel) + { + liveTvChannel.ServiceName = entity.ExternalServiceId; + } + + if (dto is Trailer trailer) + { + trailer.TrailerTypes = entity.TrailerTypes?.Select(e => (TrailerType)e.Id).ToArray() ?? []; + } + + if (dto is Video video) + { + video.PrimaryVersionId = entity.PrimaryVersionId; + } + + if (dto is IHasSeries hasSeriesName) + { + hasSeriesName.SeriesName = entity.SeriesName; + hasSeriesName.SeriesId = entity.SeriesId.GetValueOrDefault(); + hasSeriesName.SeriesPresentationUniqueKey = entity.SeriesPresentationUniqueKey; + } + + if (dto is Episode episode) + { + episode.SeasonName = entity.SeasonName; + episode.SeasonId = entity.SeasonId.GetValueOrDefault(); + } + + if (dto is IHasArtist hasArtists) + { + hasArtists.Artists = entity.Artists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? []; + } + + if (dto is IHasAlbumArtist hasAlbumArtists) + { + hasAlbumArtists.AlbumArtists = entity.AlbumArtists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? []; + } + + if (dto is LiveTvProgram program) + { + program.ShowId = entity.ShowId; + } + + if (entity.Images is not null) + { + dto.ImageInfos = entity.Images.Select(e => Map(e, appHost)).ToArray(); + } + + // dto.Type = entity.Type; + // dto.Data = entity.Data; + // dto.MediaType = Enum.TryParse<MediaType>(entity.MediaType); + if (dto is IHasStartDate hasStartDate) + { + hasStartDate.StartDate = entity.StartDate.GetValueOrDefault(); + } + + // Fields that are present in the DB but are never actually used + // dto.UnratedType = entity.UnratedType; + // dto.TopParentId = entity.TopParentId; + // dto.CleanName = entity.CleanName; + // dto.UserDataKey = entity.UserDataKey; + + if (dto is Folder folder) + { + folder.DateLastMediaAdded = entity.DateLastMediaAdded ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); + } + + return dto; + } + + /// <summary> + /// Maps a Entity to the DTO. + /// </summary> + /// <param name="dto">The entity.</param> + /// <returns>The dto to map.</returns> + public BaseItemEntity Map(BaseItemDto dto) + { + var dtoType = dto.GetType(); + var entity = new BaseItemEntity() + { + Type = dtoType.ToString(), + Id = dto.Id + }; + + if (TypeRequiresDeserialization(dtoType)) + { + entity.Data = JsonSerializer.Serialize(dto, dtoType, JsonDefaults.Options); + } + + entity.ParentId = !dto.ParentId.IsEmpty() ? dto.ParentId : null; + entity.Path = GetPathToSave(dto.Path); + entity.EndDate = dto.EndDate; + entity.CommunityRating = dto.CommunityRating; + entity.CustomRating = dto.CustomRating; + entity.IndexNumber = dto.IndexNumber; + entity.IsLocked = dto.IsLocked; + entity.Name = dto.Name; + entity.CleanName = GetCleanValue(dto.Name); + entity.OfficialRating = dto.OfficialRating; + entity.Overview = dto.Overview; + entity.ParentIndexNumber = dto.ParentIndexNumber; + entity.PremiereDate = dto.PremiereDate; + entity.ProductionYear = dto.ProductionYear; + entity.SortName = dto.SortName; + entity.ForcedSortName = dto.ForcedSortName; + entity.RunTimeTicks = dto.RunTimeTicks; + entity.PreferredMetadataLanguage = dto.PreferredMetadataLanguage; + entity.PreferredMetadataCountryCode = dto.PreferredMetadataCountryCode; + entity.IsInMixedFolder = dto.IsInMixedFolder; + entity.InheritedParentalRatingValue = dto.InheritedParentalRatingValue; + entity.InheritedParentalRatingSubValue = dto.InheritedParentalRatingSubValue; + entity.CriticRating = dto.CriticRating; + entity.PresentationUniqueKey = dto.PresentationUniqueKey; + entity.OriginalTitle = dto.OriginalTitle; + entity.Album = dto.Album; + entity.LUFS = dto.LUFS; + entity.NormalizationGain = dto.NormalizationGain; + entity.IsVirtualItem = dto.IsVirtualItem; + entity.ExternalSeriesId = dto.ExternalSeriesId; + entity.Tagline = dto.Tagline; + entity.TotalBitrate = dto.TotalBitrate; + entity.ExternalId = dto.ExternalId; + entity.Size = dto.Size; + entity.Genres = string.Join('|', dto.Genres); + entity.DateCreated = dto.DateCreated == DateTime.MinValue ? null : dto.DateCreated; + entity.DateModified = dto.DateModified == DateTime.MinValue ? null : dto.DateModified; + entity.ChannelId = dto.ChannelId; + entity.DateLastRefreshed = dto.DateLastRefreshed == DateTime.MinValue ? null : dto.DateLastRefreshed; + entity.DateLastSaved = dto.DateLastSaved == DateTime.MinValue ? null : dto.DateLastSaved; + entity.OwnerId = dto.OwnerId.ToString(); + entity.Width = dto.Width; + entity.Height = dto.Height; + entity.Provider = dto.ProviderIds.Select(e => new BaseItemProvider() + { + Item = entity, + ProviderId = e.Key, + ProviderValue = e.Value + }).ToList(); + + if (dto.Audio.HasValue) + { + entity.Audio = (ProgramAudioEntity)dto.Audio; + } + + if (dto.ExtraType.HasValue) + { + entity.ExtraType = (BaseItemExtraType)dto.ExtraType; + } + + entity.ExtraIds = dto.ExtraIds is not null ? string.Join('|', dto.ExtraIds) : null; + entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations.Where(p => !string.IsNullOrWhiteSpace(p))) : null; + entity.Studios = dto.Studios is not null ? string.Join('|', dto.Studios) : null; + entity.Tags = dto.Tags is not null ? string.Join('|', dto.Tags) : null; + entity.LockedFields = dto.LockedFields is not null ? dto.LockedFields + .Select(e => new BaseItemMetadataField() + { + Id = (int)e, + Item = entity, + ItemId = entity.Id + }) + .ToArray() : null; + + if (dto is IHasProgramAttributes hasProgramAttributes) + { + entity.IsMovie = hasProgramAttributes.IsMovie; + entity.IsSeries = hasProgramAttributes.IsSeries; + entity.EpisodeTitle = hasProgramAttributes.EpisodeTitle; + entity.IsRepeat = hasProgramAttributes.IsRepeat; + } + + if (dto is LiveTvChannel liveTvChannel) + { + entity.ExternalServiceId = liveTvChannel.ServiceName; + } + + if (dto is Video video) + { + entity.PrimaryVersionId = video.PrimaryVersionId; + } + + if (dto is IHasSeries hasSeriesName) + { + entity.SeriesName = hasSeriesName.SeriesName; + entity.SeriesId = hasSeriesName.SeriesId; + entity.SeriesPresentationUniqueKey = hasSeriesName.SeriesPresentationUniqueKey; + } + + if (dto is Episode episode) + { + entity.SeasonName = episode.SeasonName; + entity.SeasonId = episode.SeasonId; + } + + if (dto is IHasArtist hasArtists) + { + entity.Artists = hasArtists.Artists is not null ? string.Join('|', hasArtists.Artists) : null; + } + + if (dto is IHasAlbumArtist hasAlbumArtists) + { + entity.AlbumArtists = hasAlbumArtists.AlbumArtists is not null ? string.Join('|', hasAlbumArtists.AlbumArtists) : null; + } + + if (dto is LiveTvProgram program) + { + entity.ShowId = program.ShowId; + } + + if (dto.ImageInfos is not null) + { + entity.Images = dto.ImageInfos.Select(f => Map(dto.Id, f)).ToArray(); + } + + if (dto is Trailer trailer) + { + entity.TrailerTypes = trailer.TrailerTypes?.Select(e => new BaseItemTrailerType() + { + Id = (int)e, + Item = entity, + ItemId = entity.Id + }).ToArray() ?? []; + } + + // dto.Type = entity.Type; + // dto.Data = entity.Data; + entity.MediaType = dto.MediaType.ToString(); + if (dto is IHasStartDate hasStartDate) + { + entity.StartDate = hasStartDate.StartDate; + } + + entity.UnratedType = dto.GetBlockUnratedType().ToString(); + + // Fields that are present in the DB but are never actually used + // dto.UserDataKey = entity.UserDataKey; + + if (dto is Folder folder) + { + entity.DateLastMediaAdded = folder.DateLastMediaAdded == DateTime.MinValue ? null : folder.DateLastMediaAdded; + entity.IsFolder = folder.IsFolder; + } + + return entity; + } + + private string[] GetItemValueNames(IReadOnlyList<ItemValueType> itemValueTypes, IReadOnlyList<string> withItemTypes, IReadOnlyList<string> excludeItemTypes) + { + using var context = _dbProvider.CreateDbContext(); + + var query = context.ItemValuesMap + .AsNoTracking() + .Where(e => itemValueTypes.Any(w => (ItemValueType)w == e.ItemValue.Type)); + if (withItemTypes.Count > 0) + { + query = query.Where(e => withItemTypes.Contains(e.Item.Type)); + } + + if (excludeItemTypes.Count > 0) + { + query = query.Where(e => !excludeItemTypes.Contains(e.Item.Type)); + } + + // query = query.DistinctBy(e => e.CleanValue); + return query.Select(e => e.ItemValue) + .GroupBy(e => e.CleanValue) + .Select(e => e.First().Value) + .ToArray(); + } + + private static bool TypeRequiresDeserialization(Type type) + { + return type.GetCustomAttribute<RequiresSourceSerialisationAttribute>() == null; + } + + private BaseItemDto? DeserializeBaseItem(BaseItemEntity baseItemEntity, bool skipDeserialization = false) + { + ArgumentNullException.ThrowIfNull(baseItemEntity, nameof(baseItemEntity)); + if (_serverConfigurationManager?.Configuration is null) + { + throw new InvalidOperationException("Server Configuration manager or configuration is null"); + } + + var typeToSerialise = GetType(baseItemEntity.Type); + return BaseItemRepository.DeserializeBaseItem( + baseItemEntity, + _logger, + _appHost, + skipDeserialization || (_serverConfigurationManager.Configuration.SkipDeserializationForBasicTypes && (typeToSerialise == typeof(Channel) || typeToSerialise == typeof(UserRootFolder)))); + } + + /// <summary> + /// Deserializes a BaseItemEntity and sets all properties. + /// </summary> + /// <param name="baseItemEntity">The DB entity.</param> + /// <param name="logger">Logger.</param> + /// <param name="appHost">The application server Host.</param> + /// <param name="skipDeserialization">If only mapping should be processed.</param> + /// <returns>A mapped BaseItem, or null if the item type is unknown.</returns> + public static BaseItemDto? DeserializeBaseItem(BaseItemEntity baseItemEntity, ILogger logger, IServerApplicationHost? appHost, bool skipDeserialization = false) + { + var type = GetType(baseItemEntity.Type); + if (type is null) + { + logger.LogWarning( + "Skipping item {ItemId} with unknown type '{ItemType}'. This may indicate a removed plugin or database corruption.", + baseItemEntity.Id, + baseItemEntity.Type); + return null; + } + + BaseItemDto? dto = null; + if (TypeRequiresDeserialization(type) && baseItemEntity.Data is not null && !skipDeserialization) + { + try + { + dto = JsonSerializer.Deserialize(baseItemEntity.Data, type, JsonDefaults.Options) as BaseItemDto; + } + catch (JsonException ex) + { + logger.LogError(ex, "Error deserializing item with JSON: {Data}", baseItemEntity.Data); + } + } + + if (dto is null) + { + dto = Activator.CreateInstance(type) as BaseItemDto ?? throw new InvalidOperationException("Cannot deserialize unknown type."); + } + + return Map(baseItemEntity, dto, appHost, logger); + } + + private QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetItemValues(InternalItemsQuery filter, IReadOnlyList<ItemValueType> itemValueTypes, string returnType) + { + ArgumentNullException.ThrowIfNull(filter); + + if (!(filter.Limit.HasValue && filter.Limit.Value > 0)) + { + filter.EnableTotalRecordCount = false; + } + + using var context = _dbProvider.CreateDbContext(); + + var innerQueryFilter = TranslateQuery(context.BaseItems.Where(e => e.Id != EF.Constant(PlaceholderId)), context, new InternalItemsQuery(filter.User) + { + ExcludeItemTypes = filter.ExcludeItemTypes, + IncludeItemTypes = filter.IncludeItemTypes, + MediaTypes = filter.MediaTypes, + AncestorIds = filter.AncestorIds, + ItemIds = filter.ItemIds, + TopParentIds = filter.TopParentIds, + ParentId = filter.ParentId, + IsAiring = filter.IsAiring, + IsMovie = filter.IsMovie, + IsSports = filter.IsSports, + IsKids = filter.IsKids, + IsNews = filter.IsNews, + IsSeries = filter.IsSeries + }); + + var itemValuesQuery = context.ItemValues + .Where(f => itemValueTypes.Contains(f.Type)) + .SelectMany(f => f.BaseItemsMap!, (f, w) => new { f, w }) + .Join( + innerQueryFilter, + fw => fw.w.ItemId, + g => g.Id, + (fw, g) => fw.f.CleanValue); + + var innerQuery = PrepareItemQuery(context, filter) + .Where(e => e.Type == returnType) + .Where(e => itemValuesQuery.Contains(e.CleanName)); + + var outerQueryFilter = new InternalItemsQuery(filter.User) + { + IsPlayed = filter.IsPlayed, + IsFavorite = filter.IsFavorite, + IsFavoriteOrLiked = filter.IsFavoriteOrLiked, + IsLiked = filter.IsLiked, + IsLocked = filter.IsLocked, + NameLessThan = filter.NameLessThan, + NameStartsWith = filter.NameStartsWith, + NameStartsWithOrGreater = filter.NameStartsWithOrGreater, + Tags = filter.Tags, + OfficialRatings = filter.OfficialRatings, + StudioIds = filter.StudioIds, + GenreIds = filter.GenreIds, + Genres = filter.Genres, + Years = filter.Years, + NameContains = filter.NameContains, + SearchTerm = filter.SearchTerm, + ExcludeItemIds = filter.ExcludeItemIds + }; + + var masterQuery = TranslateQuery(innerQuery, context, outerQueryFilter) + .GroupBy(e => e.PresentationUniqueKey) + .Select(e => e.FirstOrDefault()) + .Select(e => e!.Id); + + var query = context.BaseItems + .Include(e => e.TrailerTypes) + .Include(e => e.Provider) + .Include(e => e.LockedFields) + .Include(e => e.Images) + .AsSingleQuery() + .Where(e => masterQuery.Contains(e.Id)); + + query = ApplyOrder(query, filter, context); + + var result = new QueryResult<(BaseItemDto, ItemCounts?)>(); + if (filter.EnableTotalRecordCount) + { + result.TotalRecordCount = query.Count(); + } + + if (filter.StartIndex.HasValue && filter.StartIndex.Value > 0) + { + query = query.Skip(filter.StartIndex.Value); + } + + if (filter.Limit.HasValue && filter.Limit.Value > 0) + { + query = query.Take(filter.Limit.Value); + } + + IQueryable<BaseItemEntity>? itemCountQuery = null; + + if (filter.IncludeItemTypes.Length > 0) + { + // if we are to include more then one type, sub query those items beforehand. + + var typeSubQuery = new InternalItemsQuery(filter.User) + { + ExcludeItemTypes = filter.ExcludeItemTypes, + IncludeItemTypes = filter.IncludeItemTypes, + MediaTypes = filter.MediaTypes, + AncestorIds = filter.AncestorIds, + ExcludeItemIds = filter.ExcludeItemIds, + ItemIds = filter.ItemIds, + TopParentIds = filter.TopParentIds, + ParentId = filter.ParentId, + IsPlayed = filter.IsPlayed + }; + + itemCountQuery = TranslateQuery(context.BaseItems.AsNoTracking().Where(e => e.Id != EF.Constant(PlaceholderId)), context, typeSubQuery) + .Where(e => e.ItemValues!.Any(f => itemValueTypes!.Contains(f.ItemValue.Type))); + + var seriesTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Series]; + var movieTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Movie]; + var episodeTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Episode]; + var musicAlbumTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicAlbum]; + var musicArtistTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]; + var audioTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Audio]; + var trailerTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Trailer]; + + var resultQuery = query.Select(e => new + { + item = e, + // TODO: This is bad refactor! + itemCount = new ItemCounts() + { + SeriesCount = itemCountQuery!.Count(f => f.Type == seriesTypeName), + EpisodeCount = itemCountQuery!.Count(f => f.Type == episodeTypeName), + MovieCount = itemCountQuery!.Count(f => f.Type == movieTypeName), + AlbumCount = itemCountQuery!.Count(f => f.Type == musicAlbumTypeName), + ArtistCount = itemCountQuery!.Count(f => f.Type == musicArtistTypeName), + SongCount = itemCountQuery!.Count(f => f.Type == audioTypeName), + TrailerCount = itemCountQuery!.Count(f => f.Type == trailerTypeName), + } + }); + + result.StartIndex = filter.StartIndex ?? 0; + result.Items = + [ + .. resultQuery + .AsEnumerable() + .Where(e => e is not null) + .Select(e => (Item: DeserializeBaseItem(e.item, filter.SkipDeserialization), e.itemCount)) + .Where(e => e.Item is not null) + .Select(e => (e.Item!, e.itemCount)) + ]; + } + else + { + result.StartIndex = filter.StartIndex ?? 0; + result.Items = + [ + .. query + .AsEnumerable() + .Where(e => e is not null) + .Select(e => (Item: DeserializeBaseItem(e, filter.SkipDeserialization), ItemCounts: (ItemCounts?)null)) + .Where(e => e.Item is not null) + .Select(e => (e.Item!, e.ItemCounts)) + ]; + } + + return result; + } + + private static void PrepareFilterQuery(InternalItemsQuery query) + { + if (query.Limit.HasValue && query.Limit.Value > 0 && query.EnableGroupByMetadataKey) + { + query.Limit = query.Limit.Value + 4; + } + + if (query.IsResumable ?? false) + { + query.IsVirtualItem = false; + } + } + + /// <summary> + /// Gets the clean value for search and sorting purposes. + /// </summary> + /// <param name="value">The value to clean.</param> + /// <returns>The cleaned value.</returns> + public static string GetCleanValue(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return value; + } + + var noDiacritics = value.RemoveDiacritics(); + + // Build a string where any punctuation or symbol is treated as a separator (space). + var sb = new StringBuilder(noDiacritics.Length); + var previousWasSpace = false; + foreach (var ch in noDiacritics) + { + char outCh; + if (char.IsLetterOrDigit(ch) || char.IsWhiteSpace(ch)) + { + outCh = ch; + } + else + { + outCh = ' '; + } + + // normalize any whitespace character to a single ASCII space. + if (char.IsWhiteSpace(outCh)) + { + if (!previousWasSpace) + { + sb.Append(' '); + previousWasSpace = true; + } + } + else + { + sb.Append(outCh); + previousWasSpace = false; + } + } + + // trim leading/trailing spaces that may have been added. + var collapsed = sb.ToString().Trim(); + return collapsed.ToLowerInvariant(); + } + + private List<(ItemValueType MagicNumber, string Value)> GetItemValuesToSave(BaseItemDto item, List<string> inheritedTags) + { + var list = new List<(ItemValueType, string)>(); + + if (item is IHasArtist hasArtist) + { + list.AddRange(hasArtist.Artists.Select(i => ((ItemValueType)0, i))); + } + + if (item is IHasAlbumArtist hasAlbumArtist) + { + list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => (ItemValueType.AlbumArtist, i))); + } + + list.AddRange(item.Genres.Select(i => (ItemValueType.Genre, i))); + list.AddRange(item.Studios.Select(i => (ItemValueType.Studios, i))); + list.AddRange(item.Tags.Select(i => (ItemValueType.Tags, i))); + + // keywords was 5 + + list.AddRange(inheritedTags.Select(i => (ItemValueType.InheritedTags, i))); + + // Remove all invalid values. + list.RemoveAll(i => string.IsNullOrWhiteSpace(i.Item2)); + + return list; + } + + private static BaseItemImageInfo Map(Guid baseItemId, ItemImageInfo e) + { + return new BaseItemImageInfo() + { + ItemId = baseItemId, + Id = Guid.NewGuid(), + Path = e.Path, + Blurhash = e.BlurHash is null ? null : Encoding.UTF8.GetBytes(e.BlurHash), + DateModified = e.DateModified, + Height = e.Height, + Width = e.Width, + ImageType = (ImageInfoImageType)e.Type, + Item = null! + }; + } + + private static ItemImageInfo Map(BaseItemImageInfo e, IServerApplicationHost? appHost) + { + return new ItemImageInfo() + { + Path = appHost?.ExpandVirtualPath(e.Path) ?? e.Path, + BlurHash = e.Blurhash is null ? null : Encoding.UTF8.GetString(e.Blurhash), + DateModified = e.DateModified ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc), + Height = e.Height, + Width = e.Width, + Type = (ImageType)e.ImageType + }; + } + + private string? GetPathToSave(string path) + { + if (path is null) + { + return null; + } + + return _appHost.ReverseVirtualPath(path); + } + + private List<string> GetItemByNameTypesInQuery(InternalItemsQuery query) + { + var list = new List<string>(); + + if (IsTypeInQuery(BaseItemKind.Person, query)) + { + list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.Person]!); + } + + if (IsTypeInQuery(BaseItemKind.Genre, query)) + { + list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.Genre]!); + } + + if (IsTypeInQuery(BaseItemKind.MusicGenre, query)) + { + list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicGenre]!); + } + + if (IsTypeInQuery(BaseItemKind.MusicArtist, query)) + { + list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!); + } + + if (IsTypeInQuery(BaseItemKind.Studio, query)) + { + list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.Studio]!); + } + + return list; + } + + private bool IsTypeInQuery(BaseItemKind type, InternalItemsQuery query) + { + if (query.ExcludeItemTypes.Contains(type)) + { + return false; + } + + return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type); + } + + private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query) + { + if (!query.GroupByPresentationUniqueKey) + { + return false; + } + + if (query.GroupBySeriesPresentationUniqueKey) + { + return false; + } + + if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey)) + { + return false; + } + + if (query.User is null) + { + return false; + } + + if (query.IncludeItemTypes.Length == 0) + { + return true; + } + + return query.IncludeItemTypes.Contains(BaseItemKind.Episode) + || query.IncludeItemTypes.Contains(BaseItemKind.Video) + || query.IncludeItemTypes.Contains(BaseItemKind.Movie) + || query.IncludeItemTypes.Contains(BaseItemKind.MusicVideo) + || query.IncludeItemTypes.Contains(BaseItemKind.Series) + || query.IncludeItemTypes.Contains(BaseItemKind.Season); + } + + private IQueryable<BaseItemEntity> ApplyOrder(IQueryable<BaseItemEntity> query, InternalItemsQuery filter, JellyfinDbContext context) + { + var orderBy = filter.OrderBy.Where(e => e.OrderBy != ItemSortBy.Default).ToArray(); + var hasSearch = !string.IsNullOrEmpty(filter.SearchTerm); + + if (hasSearch) + { + orderBy = [(ItemSortBy.SortName, SortOrder.Ascending), .. orderBy]; + } + else if (orderBy.Length == 0) + { + return query.OrderBy(e => e.SortName); + } + + IOrderedQueryable<BaseItemEntity>? orderedQuery = null; + + // When searching, prioritize by match quality: exact match > prefix match > contains + if (hasSearch) + { + orderedQuery = query.OrderBy(OrderMapper.MapSearchRelevanceOrder(filter.SearchTerm!)); + } + + var firstOrdering = orderBy.FirstOrDefault(); + if (firstOrdering != default) + { + var expression = OrderMapper.MapOrderByField(firstOrdering.OrderBy, filter, context); + if (orderedQuery is null) + { + // No search relevance ordering, start fresh + orderedQuery = firstOrdering.SortOrder == SortOrder.Ascending + ? query.OrderBy(expression) + : query.OrderByDescending(expression); + } + else + { + // Search relevance ordering already applied, chain with ThenBy + orderedQuery = firstOrdering.SortOrder == SortOrder.Ascending + ? orderedQuery.ThenBy(expression) + : orderedQuery.ThenByDescending(expression); + } + + if (firstOrdering.OrderBy is ItemSortBy.Default or ItemSortBy.SortName) + { + orderedQuery = firstOrdering.SortOrder is SortOrder.Ascending + ? orderedQuery.ThenBy(e => e.Name) + : orderedQuery.ThenByDescending(e => e.Name); + } + } + + foreach (var item in orderBy.Skip(1)) + { + var expression = OrderMapper.MapOrderByField(item.OrderBy, filter, context); + if (item.SortOrder == SortOrder.Ascending) + { + orderedQuery = orderedQuery!.ThenBy(expression); + } + else + { + orderedQuery = orderedQuery!.ThenByDescending(expression); + } + } + + return orderedQuery ?? query; + } + + private IQueryable<BaseItemEntity> TranslateQuery( + IQueryable<BaseItemEntity> baseQuery, + JellyfinDbContext context, + InternalItemsQuery filter) + { + const int HDWidth = 1200; + const int UHDWidth = 3800; + const int UHDHeight = 2100; + + var minWidth = filter.MinWidth; + var maxWidth = filter.MaxWidth; + var now = DateTime.UtcNow; + + if (filter.IsHD.HasValue || filter.Is4K.HasValue) + { + bool includeSD = false; + bool includeHD = false; + bool include4K = false; + + if (filter.IsHD.HasValue && !filter.IsHD.Value) + { + includeSD = true; + } + + if (filter.IsHD.HasValue && filter.IsHD.Value) + { + includeHD = true; + } + + if (filter.Is4K.HasValue && filter.Is4K.Value) + { + include4K = true; + } + + baseQuery = baseQuery.Where(e => + (includeSD && e.Width < HDWidth) || + (includeHD && e.Width >= HDWidth && !(e.Width >= UHDWidth || e.Height >= UHDHeight)) || + (include4K && (e.Width >= UHDWidth || e.Height >= UHDHeight))); + } + + if (minWidth.HasValue) + { + baseQuery = baseQuery.Where(e => e.Width >= minWidth); + } + + if (filter.MinHeight.HasValue) + { + baseQuery = baseQuery.Where(e => e.Height >= filter.MinHeight); + } + + if (maxWidth.HasValue) + { + baseQuery = baseQuery.Where(e => e.Width <= maxWidth); + } + + if (filter.MaxHeight.HasValue) + { + baseQuery = baseQuery.Where(e => e.Height <= filter.MaxHeight); + } + + if (filter.IsLocked.HasValue) + { + baseQuery = baseQuery.Where(e => e.IsLocked == filter.IsLocked); + } + + var tags = filter.Tags.ToList(); + var excludeTags = filter.ExcludeTags.ToList(); + + if (filter.IsMovie.HasValue) + { + var shouldIncludeAllMovieTypes = filter.IsMovie.Value + && (filter.IncludeItemTypes.Length == 0 + || filter.IncludeItemTypes.Contains(BaseItemKind.Movie) + || filter.IncludeItemTypes.Contains(BaseItemKind.Trailer)); + + if (!shouldIncludeAllMovieTypes) + { + baseQuery = baseQuery.Where(e => e.IsMovie == filter.IsMovie.Value); + } + } + + if (filter.IsSeries.HasValue) + { + baseQuery = baseQuery.Where(e => e.IsSeries == filter.IsSeries); + } + + if (filter.IsSports.HasValue) + { + if (filter.IsSports.Value) + { + tags.Add("Sports"); + } + else + { + excludeTags.Add("Sports"); + } + } + + if (filter.IsNews.HasValue) + { + if (filter.IsNews.Value) + { + tags.Add("News"); + } + else + { + excludeTags.Add("News"); + } + } + + if (filter.IsKids.HasValue) + { + if (filter.IsKids.Value) + { + tags.Add("Kids"); + } + else + { + excludeTags.Add("Kids"); + } + } + + if (!string.IsNullOrEmpty(filter.SearchTerm)) + { + var cleanedSearchTerm = GetCleanValue(filter.SearchTerm); + var originalSearchTerm = filter.SearchTerm.ToLower(); + if (SearchWildcardTerms.Any(f => cleanedSearchTerm.Contains(f))) + { + cleanedSearchTerm = $"%{cleanedSearchTerm.Trim('%')}%"; + baseQuery = baseQuery.Where(e => EF.Functions.Like(e.CleanName!, cleanedSearchTerm) || (e.OriginalTitle != null && EF.Functions.Like(e.OriginalTitle.ToLower(), originalSearchTerm))); + } + else + { + baseQuery = baseQuery.Where(e => e.CleanName!.Contains(cleanedSearchTerm) || (e.OriginalTitle != null && e.OriginalTitle.ToLower().Contains(originalSearchTerm))); + } + } + + if (filter.IsFolder.HasValue) + { + baseQuery = baseQuery.Where(e => e.IsFolder == filter.IsFolder); + } + + var includeTypes = filter.IncludeItemTypes; + + // Only specify excluded types if no included types are specified + if (filter.IncludeItemTypes.Length == 0) + { + var excludeTypes = filter.ExcludeItemTypes; + if (excludeTypes.Length == 1) + { + if (_itemTypeLookup.BaseItemKindNames.TryGetValue(excludeTypes[0], out var excludeTypeName)) + { + baseQuery = baseQuery.Where(e => e.Type != excludeTypeName); + } + } + else if (excludeTypes.Length > 1) + { + var excludeTypeName = new List<string>(); + foreach (var excludeType in excludeTypes) + { + if (_itemTypeLookup.BaseItemKindNames.TryGetValue(excludeType, out var baseItemKindName)) + { + excludeTypeName.Add(baseItemKindName!); + } + } + + baseQuery = baseQuery.Where(e => !excludeTypeName.Contains(e.Type)); + } + } + else + { + string[] types = includeTypes.Select(f => _itemTypeLookup.BaseItemKindNames.GetValueOrDefault(f)).Where(e => e != null).ToArray()!; + baseQuery = baseQuery.WhereOneOrMany(types, f => f.Type); + } + + if (filter.ChannelIds.Count > 0) + { + baseQuery = baseQuery.Where(e => e.ChannelId != null && filter.ChannelIds.Contains(e.ChannelId.Value)); + } + + if (!filter.ParentId.IsEmpty()) + { + baseQuery = baseQuery.Where(e => e.ParentId!.Value == filter.ParentId); + } + + if (!string.IsNullOrWhiteSpace(filter.Path)) + { + var pathToQuery = GetPathToSave(filter.Path); + baseQuery = baseQuery.Where(e => e.Path == pathToQuery); + } + + if (!string.IsNullOrWhiteSpace(filter.PresentationUniqueKey)) + { + baseQuery = baseQuery.Where(e => e.PresentationUniqueKey == filter.PresentationUniqueKey); + } + + if (filter.MinCommunityRating.HasValue) + { + baseQuery = baseQuery.Where(e => e.CommunityRating >= filter.MinCommunityRating); + } + + if (filter.MinIndexNumber.HasValue) + { + baseQuery = baseQuery.Where(e => e.IndexNumber >= filter.MinIndexNumber); + } + + if (filter.MinParentAndIndexNumber.HasValue) + { + baseQuery = baseQuery + .Where(e => (e.ParentIndexNumber == filter.MinParentAndIndexNumber.Value.ParentIndexNumber && e.IndexNumber >= filter.MinParentAndIndexNumber.Value.IndexNumber) || e.ParentIndexNumber > filter.MinParentAndIndexNumber.Value.ParentIndexNumber); + } + + if (filter.MinDateCreated.HasValue) + { + baseQuery = baseQuery.Where(e => e.DateCreated >= filter.MinDateCreated); + } + + if (filter.MinDateLastSaved.HasValue) + { + baseQuery = baseQuery.Where(e => e.DateLastSaved != null && e.DateLastSaved >= filter.MinDateLastSaved.Value); + } + + if (filter.MinDateLastSavedForUser.HasValue) + { + baseQuery = baseQuery.Where(e => e.DateLastSaved != null && e.DateLastSaved >= filter.MinDateLastSavedForUser.Value); + } + + if (filter.IndexNumber.HasValue) + { + baseQuery = baseQuery.Where(e => e.IndexNumber == filter.IndexNumber.Value); + } + + if (filter.ParentIndexNumber.HasValue) + { + baseQuery = baseQuery.Where(e => e.ParentIndexNumber == filter.ParentIndexNumber.Value); + } + + if (filter.ParentIndexNumberNotEquals.HasValue) + { + baseQuery = baseQuery.Where(e => e.ParentIndexNumber != filter.ParentIndexNumberNotEquals.Value || e.ParentIndexNumber == null); + } + + var minEndDate = filter.MinEndDate; + var maxEndDate = filter.MaxEndDate; + + if (filter.HasAired.HasValue) + { + if (filter.HasAired.Value) + { + maxEndDate = DateTime.UtcNow; + } + else + { + minEndDate = DateTime.UtcNow; + } + } + + if (minEndDate.HasValue) + { + baseQuery = baseQuery.Where(e => e.EndDate >= minEndDate); + } + + if (maxEndDate.HasValue) + { + baseQuery = baseQuery.Where(e => e.EndDate <= maxEndDate); + } + + if (filter.MinStartDate.HasValue) + { + baseQuery = baseQuery.Where(e => e.StartDate >= filter.MinStartDate.Value); + } + + if (filter.MaxStartDate.HasValue) + { + baseQuery = baseQuery.Where(e => e.StartDate <= filter.MaxStartDate.Value); + } + + if (filter.MinPremiereDate.HasValue) + { + baseQuery = baseQuery.Where(e => e.PremiereDate >= filter.MinPremiereDate.Value); + } + + if (filter.MaxPremiereDate.HasValue) + { + baseQuery = baseQuery.Where(e => e.PremiereDate <= filter.MaxPremiereDate.Value); + } + + if (filter.TrailerTypes.Length > 0) + { + var trailerTypes = filter.TrailerTypes.Select(e => (int)e).ToArray(); + baseQuery = baseQuery.Where(e => trailerTypes.Any(f => e.TrailerTypes!.Any(w => w.Id == f))); + } + + if (filter.IsAiring.HasValue) + { + if (filter.IsAiring.Value) + { + baseQuery = baseQuery.Where(e => e.StartDate <= now && e.EndDate >= now); + } + else + { + baseQuery = baseQuery.Where(e => e.StartDate > now && e.EndDate < now); + } + } + + if (filter.PersonIds.Length > 0) + { + var peopleEntityIds = context.BaseItems + .WhereOneOrMany(filter.PersonIds, b => b.Id) + .Join( + context.Peoples, + b => b.Name, + p => p.Name, + (b, p) => p.Id); + + baseQuery = baseQuery + .Where(e => context.PeopleBaseItemMap + .Any(m => m.ItemId == e.Id && peopleEntityIds.Contains(m.PeopleId))); + } + + if (!string.IsNullOrWhiteSpace(filter.Person)) + { + baseQuery = baseQuery.Where(e => e.Peoples!.Any(f => f.People.Name == filter.Person)); + } + + if (!string.IsNullOrWhiteSpace(filter.MinSortName)) + { + // this does not makes sense. + // baseQuery = baseQuery.Where(e => e.SortName >= query.MinSortName); + // whereClauses.Add("SortName>=@MinSortName"); + // statement?.TryBind("@MinSortName", query.MinSortName); + } + + if (!string.IsNullOrWhiteSpace(filter.ExternalSeriesId)) + { + baseQuery = baseQuery.Where(e => e.ExternalSeriesId == filter.ExternalSeriesId); + } + + if (!string.IsNullOrWhiteSpace(filter.ExternalId)) + { + baseQuery = baseQuery.Where(e => e.ExternalId == filter.ExternalId); + } + + if (!string.IsNullOrWhiteSpace(filter.Name)) + { + if (filter.UseRawName == true) + { + baseQuery = baseQuery.Where(e => e.Name == filter.Name); + } + else + { + var cleanName = GetCleanValue(filter.Name); + baseQuery = baseQuery.Where(e => e.CleanName == cleanName); + } + } + + // These are the same, for now + var nameContains = filter.NameContains; + if (!string.IsNullOrWhiteSpace(nameContains)) + { + if (SearchWildcardTerms.Any(f => nameContains.Contains(f))) + { + nameContains = $"%{nameContains.Trim('%')}%"; + baseQuery = baseQuery.Where(e => EF.Functions.Like(e.CleanName, nameContains) || EF.Functions.Like(e.OriginalTitle, nameContains)); + } + else + { + baseQuery = baseQuery.Where(e => + e.CleanName!.Contains(nameContains) + || e.OriginalTitle!.ToLower().Contains(nameContains!)); + } + } + + if (!string.IsNullOrWhiteSpace(filter.NameStartsWith)) + { + var startsWithLower = filter.NameStartsWith.ToLowerInvariant(); + baseQuery = baseQuery.Where(e => e.SortName!.StartsWith(startsWithLower)); + } + + if (!string.IsNullOrWhiteSpace(filter.NameStartsWithOrGreater)) + { + var startsOrGreaterLower = filter.NameStartsWithOrGreater.ToLowerInvariant(); + baseQuery = baseQuery.Where(e => e.SortName!.CompareTo(startsOrGreaterLower) >= 0); + } + + if (!string.IsNullOrWhiteSpace(filter.NameLessThan)) + { + var lessThanLower = filter.NameLessThan.ToLowerInvariant(); + baseQuery = baseQuery.Where(e => e.SortName!.CompareTo(lessThanLower ) < 0); + } + + if (filter.ImageTypes.Length > 0) + { + var imgTypes = filter.ImageTypes.Select(e => (ImageInfoImageType)e).ToArray(); + baseQuery = baseQuery.Where(e => imgTypes.Any(f => e.Images!.Any(w => w.ImageType == f))); + } + + if (filter.IsLiked.HasValue) + { + baseQuery = baseQuery + .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.Rating >= UserItemData.MinLikeValue); + } + + if (filter.IsFavoriteOrLiked.HasValue) + { + baseQuery = baseQuery + .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.IsFavorite == filter.IsFavoriteOrLiked); + } + + if (filter.IsFavorite.HasValue) + { + baseQuery = baseQuery + .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.IsFavorite == filter.IsFavorite); + } + + if (filter.IsPlayed.HasValue) + { + // We should probably figure this out for all folders, but for right now, this is the only place where we need it + if (filter.IncludeItemTypes.Length == 1 && filter.IncludeItemTypes[0] == BaseItemKind.Series) + { + baseQuery = baseQuery.Where(e => context.BaseItems.Where(e => e.Id != EF.Constant(PlaceholderId)) + .Where(e => e.IsFolder == false && e.IsVirtualItem == false) + .Where(f => f.UserData!.FirstOrDefault(e => e.UserId == filter.User!.Id && e.Played)!.Played) + .Any(f => f.SeriesPresentationUniqueKey == e.PresentationUniqueKey) == filter.IsPlayed); + } + else + { + baseQuery = baseQuery + .Select(e => new + { + IsPlayed = e.UserData!.Where(f => f.UserId == filter.User!.Id).Select(f => (bool?)f.Played).FirstOrDefault() ?? false, + Item = e + }) + .Where(e => e.IsPlayed == filter.IsPlayed) + .Select(f => f.Item); + } + } + + if (filter.IsResumable.HasValue) + { + if (filter.IsResumable.Value) + { + baseQuery = baseQuery + .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.PlaybackPositionTicks > 0); + } + else + { + baseQuery = baseQuery + .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.PlaybackPositionTicks == 0); + } + } + + if (filter.ArtistIds.Length > 0) + { + baseQuery = baseQuery.WhereReferencedItemMultipleTypes(context, [ItemValueType.Artist, ItemValueType.AlbumArtist], filter.ArtistIds); + } + + if (filter.AlbumArtistIds.Length > 0) + { + baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.AlbumArtist, filter.AlbumArtistIds); + } + + if (filter.ContributingArtistIds.Length > 0) + { + var contributingNames = context.BaseItems + .Where(b => filter.ContributingArtistIds.Contains(b.Id)) + .Select(b => b.CleanName); + + baseQuery = baseQuery.Where(e => + e.ItemValues!.Any(ivm => + ivm.ItemValue.Type == ItemValueType.Artist && + contributingNames.Contains(ivm.ItemValue.CleanValue)) + && + !e.ItemValues!.Any(ivm => + ivm.ItemValue.Type == ItemValueType.AlbumArtist && + contributingNames.Contains(ivm.ItemValue.CleanValue))); + } + + if (filter.AlbumIds.Length > 0) + { + var subQuery = context.BaseItems.WhereOneOrMany(filter.AlbumIds, f => f.Id); + baseQuery = baseQuery.Where(e => subQuery.Any(f => f.Name == e.Album)); + } + + if (filter.ExcludeArtistIds.Length > 0) + { + baseQuery = baseQuery.WhereReferencedItemMultipleTypes(context, [ItemValueType.Artist, ItemValueType.AlbumArtist], filter.ExcludeArtistIds, true); + } + + if (filter.GenreIds.Count > 0) + { + baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Genre, filter.GenreIds.ToArray()); + } + + if (filter.Genres.Count > 0) + { + var cleanGenres = filter.Genres.Select(e => GetCleanValue(e)).ToArray().OneOrManyExpressionBuilder<ItemValueMap, string>(f => f.ItemValue.CleanValue); + baseQuery = baseQuery + .Where(e => e.ItemValues!.AsQueryable().Where(f => f.ItemValue.Type == ItemValueType.Genre).Any(cleanGenres)); + } + + if (tags.Count > 0) + { + var cleanValues = tags.Select(e => GetCleanValue(e)).ToArray().OneOrManyExpressionBuilder<ItemValueMap, string>(f => f.ItemValue.CleanValue); + baseQuery = baseQuery + .Where(e => e.ItemValues!.AsQueryable().Where(f => f.ItemValue.Type == ItemValueType.Tags).Any(cleanValues)); + } + + if (excludeTags.Count > 0) + { + var cleanValues = excludeTags.Select(e => GetCleanValue(e)).ToArray().OneOrManyExpressionBuilder<ItemValueMap, string>(f => f.ItemValue.CleanValue); + baseQuery = baseQuery + .Where(e => !e.ItemValues!.AsQueryable().Where(f => f.ItemValue.Type == ItemValueType.Tags).Any(cleanValues)); + } + + if (filter.StudioIds.Length > 0) + { + baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Studios, filter.StudioIds.ToArray()); + } + + if (filter.OfficialRatings.Length > 0) + { + baseQuery = baseQuery + .Where(e => filter.OfficialRatings.Contains(e.OfficialRating)); + } + + Expression<Func<BaseItemEntity, bool>>? minParentalRatingFilter = null; + if (filter.MinParentalRating != null) + { + var min = filter.MinParentalRating; + var minScore = min.Score; + var minSubScore = min.SubScore ?? 0; + + minParentalRatingFilter = e => + e.InheritedParentalRatingValue == null || + e.InheritedParentalRatingValue > minScore || + (e.InheritedParentalRatingValue == minScore && (e.InheritedParentalRatingSubValue ?? 0) >= minSubScore); + } + + Expression<Func<BaseItemEntity, bool>>? maxParentalRatingFilter = null; + if (filter.MaxParentalRating != null) + { + var max = filter.MaxParentalRating; + var maxScore = max.Score; + var maxSubScore = max.SubScore ?? 0; + + maxParentalRatingFilter = e => + e.InheritedParentalRatingValue == null || + e.InheritedParentalRatingValue < maxScore || + (e.InheritedParentalRatingValue == maxScore && (e.InheritedParentalRatingSubValue ?? 0) <= maxSubScore); + } + + if (filter.HasParentalRating ?? false) + { + if (minParentalRatingFilter != null) + { + baseQuery = baseQuery.Where(minParentalRatingFilter); + } + + if (maxParentalRatingFilter != null) + { + baseQuery = baseQuery.Where(maxParentalRatingFilter); + } + } + else if (filter.BlockUnratedItems.Length > 0) + { + var unratedItemTypes = filter.BlockUnratedItems.Select(f => f.ToString()).ToArray(); + Expression<Func<BaseItemEntity, bool>> unratedItemFilter = e => e.InheritedParentalRatingValue != null || !unratedItemTypes.Contains(e.UnratedType); + + if (minParentalRatingFilter != null && maxParentalRatingFilter != null) + { + baseQuery = baseQuery.Where(unratedItemFilter.And(minParentalRatingFilter.And(maxParentalRatingFilter))); + } + else if (minParentalRatingFilter != null) + { + baseQuery = baseQuery.Where(unratedItemFilter.And(minParentalRatingFilter)); + } + else if (maxParentalRatingFilter != null) + { + baseQuery = baseQuery.Where(unratedItemFilter.And(maxParentalRatingFilter)); + } + else + { + baseQuery = baseQuery.Where(unratedItemFilter); + } + } + else if (minParentalRatingFilter != null || maxParentalRatingFilter != null) + { + if (minParentalRatingFilter != null) + { + baseQuery = baseQuery.Where(minParentalRatingFilter); + } + + if (maxParentalRatingFilter != null) + { + baseQuery = baseQuery.Where(maxParentalRatingFilter); + } + } + else if (!filter.HasParentalRating ?? false) + { + baseQuery = baseQuery + .Where(e => e.InheritedParentalRatingValue == null); + } + + if (filter.HasOfficialRating.HasValue) + { + if (filter.HasOfficialRating.Value) + { + baseQuery = baseQuery + .Where(e => e.OfficialRating != null && e.OfficialRating != string.Empty); + } + else + { + baseQuery = baseQuery + .Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty); + } + } + + if (filter.HasOverview.HasValue) + { + if (filter.HasOverview.Value) + { + baseQuery = baseQuery + .Where(e => e.Overview != null && e.Overview != string.Empty); + } + else + { + baseQuery = baseQuery + .Where(e => e.Overview == null || e.Overview == string.Empty); + } + } + + if (filter.HasOwnerId.HasValue) + { + if (filter.HasOwnerId.Value) + { + baseQuery = baseQuery + .Where(e => e.OwnerId != null); + } + else + { + baseQuery = baseQuery + .Where(e => e.OwnerId == null); + } + } + + if (!string.IsNullOrWhiteSpace(filter.HasNoAudioTrackWithLanguage)) + { + baseQuery = baseQuery + .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Audio && f.Language == filter.HasNoAudioTrackWithLanguage)); + } + + if (!string.IsNullOrWhiteSpace(filter.HasNoInternalSubtitleTrackWithLanguage)) + { + baseQuery = baseQuery + .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle && !f.IsExternal && f.Language == filter.HasNoInternalSubtitleTrackWithLanguage)); + } + + if (!string.IsNullOrWhiteSpace(filter.HasNoExternalSubtitleTrackWithLanguage)) + { + baseQuery = baseQuery + .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle && f.IsExternal && f.Language == filter.HasNoExternalSubtitleTrackWithLanguage)); + } + + if (!string.IsNullOrWhiteSpace(filter.HasNoSubtitleTrackWithLanguage)) + { + baseQuery = baseQuery + .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle && f.Language == filter.HasNoSubtitleTrackWithLanguage)); + } + + if (filter.HasSubtitles.HasValue) + { + baseQuery = baseQuery + .Where(e => e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle) == filter.HasSubtitles.Value); + } + + if (filter.HasChapterImages.HasValue) + { + baseQuery = baseQuery + .Where(e => e.Chapters!.Any(f => f.ImagePath != null) == filter.HasChapterImages.Value); + } + + if (filter.HasDeadParentId.HasValue && filter.HasDeadParentId.Value) + { + baseQuery = baseQuery + .Where(e => e.ParentId.HasValue && !context.BaseItems.Where(e => e.Id != EF.Constant(PlaceholderId)).Any(f => f.Id == e.ParentId.Value)); + } + + if (filter.IsDeadArtist.HasValue && filter.IsDeadArtist.Value) + { + baseQuery = baseQuery + .Where(e => !context.ItemValues.Where(f => _getAllArtistsValueTypes.Contains(f.Type)).Any(f => f.Value == e.Name)); + } + + if (filter.IsDeadStudio.HasValue && filter.IsDeadStudio.Value) + { + baseQuery = baseQuery + .Where(e => !context.ItemValues.Where(f => _getStudiosValueTypes.Contains(f.Type)).Any(f => f.Value == e.Name)); + } + + if (filter.IsDeadGenre.HasValue && filter.IsDeadGenre.Value) + { + baseQuery = baseQuery + .Where(e => !context.ItemValues.Where(f => _getGenreValueTypes.Contains(f.Type)).Any(f => f.Value == e.Name)); + } + + if (filter.IsDeadPerson.HasValue && filter.IsDeadPerson.Value) + { + baseQuery = baseQuery + .Where(e => !context.Peoples.Any(f => f.Name == e.Name)); + } + + if (filter.Years.Length > 0) + { + baseQuery = baseQuery.WhereOneOrMany(filter.Years, e => e.ProductionYear!.Value); + } + + var isVirtualItem = filter.IsVirtualItem ?? filter.IsMissing; + if (isVirtualItem.HasValue) + { + baseQuery = baseQuery + .Where(e => e.IsVirtualItem == isVirtualItem.Value); + } + + if (filter.IsSpecialSeason.HasValue) + { + if (filter.IsSpecialSeason.Value) + { + baseQuery = baseQuery + .Where(e => e.IndexNumber == 0); + } + else + { + baseQuery = baseQuery + .Where(e => e.IndexNumber != 0); + } + } + + if (filter.IsUnaired.HasValue) + { + if (filter.IsUnaired.Value) + { + baseQuery = baseQuery + .Where(e => e.PremiereDate >= now); + } + else + { + baseQuery = baseQuery + .Where(e => e.PremiereDate < now); + } + } + + if (filter.MediaTypes.Length > 0) + { + var mediaTypes = filter.MediaTypes.Select(f => f.ToString()).ToArray(); + baseQuery = baseQuery.WhereOneOrMany(mediaTypes, e => e.MediaType); + } + + if (filter.ItemIds.Length > 0) + { + baseQuery = baseQuery.WhereOneOrMany(filter.ItemIds, e => e.Id); + } + + if (filter.ExcludeItemIds.Length > 0) + { + baseQuery = baseQuery + .Where(e => !filter.ExcludeItemIds.Contains(e.Id)); + } + + if (filter.ExcludeProviderIds is not null && filter.ExcludeProviderIds.Count > 0) + { + var exclude = filter.ExcludeProviderIds.Select(e => $"{e.Key}:{e.Value}").ToArray(); + baseQuery = baseQuery.Where(e => e.Provider!.Select(f => f.ProviderId + ":" + f.ProviderValue)!.All(f => !exclude.Contains(f))); + } + + if (filter.HasAnyProviderId is not null && filter.HasAnyProviderId.Count > 0) + { + // Allow setting a null or empty value to get all items that have the specified provider set. + var includeAny = filter.HasAnyProviderId.Where(e => string.IsNullOrEmpty(e.Value)).Select(e => e.Key).ToArray(); + if (includeAny.Length > 0) + { + baseQuery = baseQuery.Where(e => e.Provider!.Any(f => includeAny.Contains(f.ProviderId))); + } + + var includeSelected = filter.HasAnyProviderId.Where(e => !string.IsNullOrEmpty(e.Value)).Select(e => $"{e.Key}:{e.Value}").ToArray(); + if (includeSelected.Length > 0) + { + baseQuery = baseQuery.Where(e => e.Provider!.Select(f => f.ProviderId + ":" + f.ProviderValue)!.Any(f => includeSelected.Contains(f))); + } + } + + if (filter.HasImdbId.HasValue) + { + baseQuery = filter.HasImdbId.Value + ? baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId.ToLower() == MetadataProvider.Imdb.ToString().ToLower())) + : baseQuery.Where(e => e.Provider!.All(f => f.ProviderId.ToLower() != MetadataProvider.Imdb.ToString().ToLower())); + } + + if (filter.HasTmdbId.HasValue) + { + baseQuery = filter.HasTmdbId.Value + ? baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId.ToLower() == MetadataProvider.Tmdb.ToString().ToLower())) + : baseQuery.Where(e => e.Provider!.All(f => f.ProviderId.ToLower() != MetadataProvider.Tmdb.ToString().ToLower())); + } + + if (filter.HasTvdbId.HasValue) + { + baseQuery = filter.HasTvdbId.Value + ? baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId.ToLower() == MetadataProvider.Tvdb.ToString().ToLower())) + : baseQuery.Where(e => e.Provider!.All(f => f.ProviderId.ToLower() != MetadataProvider.Tvdb.ToString().ToLower())); + } + + var queryTopParentIds = filter.TopParentIds; + + if (queryTopParentIds.Length > 0) + { + var includedItemByNameTypes = GetItemByNameTypesInQuery(filter); + var enableItemsByName = (filter.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0; + if (enableItemsByName && includedItemByNameTypes.Count > 0) + { + baseQuery = baseQuery.Where(e => includedItemByNameTypes.Contains(e.Type) || queryTopParentIds.Any(w => w == e.TopParentId!.Value)); + } + else + { + baseQuery = baseQuery.WhereOneOrMany(queryTopParentIds, e => e.TopParentId!.Value); + } + } + + if (filter.AncestorIds.Length > 0) + { + baseQuery = baseQuery.Where(e => e.Parents!.Any(f => filter.AncestorIds.Contains(f.ParentItemId))); + } + + if (!string.IsNullOrWhiteSpace(filter.AncestorWithPresentationUniqueKey)) + { + baseQuery = baseQuery + .Where(e => context.BaseItems.Where(e => e.Id != EF.Constant(PlaceholderId)).Where(f => f.PresentationUniqueKey == filter.AncestorWithPresentationUniqueKey).Any(f => f.Children!.Any(w => w.ItemId == e.Id))); + } + + if (!string.IsNullOrWhiteSpace(filter.SeriesPresentationUniqueKey)) + { + baseQuery = baseQuery + .Where(e => e.SeriesPresentationUniqueKey == filter.SeriesPresentationUniqueKey); + } + + if (filter.ExcludeInheritedTags.Length > 0) + { + var excludedTags = filter.ExcludeInheritedTags; + baseQuery = baseQuery.Where(e => + !e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && excludedTags.Contains(f.ItemValue.CleanValue)) + && (!e.SeriesId.HasValue || !context.ItemValuesMap.Any(f => f.ItemId == e.SeriesId.Value && f.ItemValue.Type == ItemValueType.Tags && excludedTags.Contains(f.ItemValue.CleanValue)))); + } + + if (filter.IncludeInheritedTags.Length > 0) + { + var includeTags = filter.IncludeInheritedTags; + var isPlaylistOnlyQuery = includeTypes.Length == 1 && includeTypes.FirstOrDefault() == BaseItemKind.Playlist; + baseQuery = baseQuery.Where(e => + e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && includeTags.Contains(f.ItemValue.CleanValue)) + + // For seasons and episodes, we also need to check the parent series' tags. + || (e.SeriesId.HasValue && context.ItemValuesMap.Any(f => f.ItemId == e.SeriesId.Value && f.ItemValue.Type == ItemValueType.Tags && includeTags.Contains(f.ItemValue.CleanValue))) + + // A playlist should be accessible to its owner regardless of allowed tags + || (isPlaylistOnlyQuery && e.Data!.Contains($"OwnerUserId\":\"{filter.User!.Id:N}\""))); + } + + if (filter.SeriesStatuses.Length > 0) + { + var seriesStatus = filter.SeriesStatuses.Select(e => e.ToString()).ToArray(); + baseQuery = baseQuery + .Where(e => seriesStatus.Any(f => e.Data!.Contains(f))); + } + + if (filter.BoxSetLibraryFolders.Length > 0) + { + var boxsetFolders = filter.BoxSetLibraryFolders.Select(e => e.ToString("N", CultureInfo.InvariantCulture)).ToArray(); + baseQuery = baseQuery + .Where(e => boxsetFolders.Any(f => e.Data!.Contains(f))); + } + + if (filter.VideoTypes.Length > 0) + { + var videoTypeBs = filter.VideoTypes.Select(e => $"\"VideoType\":\"{e}\""); + baseQuery = baseQuery + .Where(e => videoTypeBs.Any(f => e.Data!.Contains(f))); + } + + if (filter.Is3D.HasValue) + { + if (filter.Is3D.Value) + { + baseQuery = baseQuery + .Where(e => e.Data!.Contains("Video3DFormat")); + } + else + { + baseQuery = baseQuery + .Where(e => !e.Data!.Contains("Video3DFormat")); + } + } + + if (filter.IsPlaceHolder.HasValue) + { + if (filter.IsPlaceHolder.Value) + { + baseQuery = baseQuery + .Where(e => e.Data!.Contains("IsPlaceHolder\":true")); + } + else + { + baseQuery = baseQuery + .Where(e => !e.Data!.Contains("IsPlaceHolder\":true")); + } + } + + if (filter.HasSpecialFeature.HasValue) + { + if (filter.HasSpecialFeature.Value) + { + baseQuery = baseQuery + .Where(e => e.ExtraIds != null); + } + else + { + baseQuery = baseQuery + .Where(e => e.ExtraIds == null); + } + } + + if (filter.HasTrailer.HasValue || filter.HasThemeSong.HasValue || filter.HasThemeVideo.HasValue) + { + if (filter.HasTrailer.GetValueOrDefault() || filter.HasThemeSong.GetValueOrDefault() || filter.HasThemeVideo.GetValueOrDefault()) + { + baseQuery = baseQuery + .Where(e => e.ExtraIds != null); + } + else + { + baseQuery = baseQuery + .Where(e => e.ExtraIds == null); + } + } + + return baseQuery; + } + + /// <inheritdoc/> + public async Task<bool> ItemExistsAsync(Guid id) + { + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + return await dbContext.BaseItems.AnyAsync(f => f.Id == id).ConfigureAwait(false); + } + } + + /// <inheritdoc/> + public bool GetIsPlayed(User user, Guid id, bool recursive) + { + using var dbContext = _dbProvider.CreateDbContext(); + + if (recursive) + { + var folderList = TraverseHirachyDown(id, dbContext, item => (item.IsFolder || item.IsVirtualItem)); + + return dbContext.BaseItems + .Where(e => folderList.Contains(e.ParentId!.Value) && !e.IsFolder && !e.IsVirtualItem) + .All(f => f.UserData!.Any(e => e.UserId == user.Id && e.Played)); + } + + return dbContext.BaseItems.Where(e => e.ParentId == id).All(f => f.UserData!.Any(e => e.UserId == user.Id && e.Played)); + } + + private static HashSet<Guid> TraverseHirachyDown(Guid parentId, JellyfinDbContext dbContext, Expression<Func<BaseItemEntity, bool>>? filter = null) + { + var folderStack = new HashSet<Guid>() + { + parentId + }; + var folderList = new HashSet<Guid>() + { + parentId + }; + + while (folderStack.Count != 0) + { + var items = folderStack.ToArray(); + folderStack.Clear(); + var query = dbContext.BaseItems + .WhereOneOrMany(items, e => e.ParentId!.Value); + + if (filter != null) + { + query = query.Where(filter); + } + + foreach (var item in query.Select(e => e.Id).ToArray()) + { + if (folderList.Add(item)) + { + folderStack.Add(item); + } + } + } + + return folderList; + } + + /// <inheritdoc/> + public IReadOnlyDictionary<string, MusicArtist[]> FindArtists(IReadOnlyList<string> artistNames) + { + using var dbContext = _dbProvider.CreateDbContext(); + + var artists = dbContext.BaseItems.Where(e => e.Type == _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!) + .Where(e => artistNames.Contains(e.Name)) + .ToArray(); + + var lookup = artists + .GroupBy(e => e.Name!) + .ToDictionary( + g => g.Key, + g => g.Select(f => DeserializeBaseItem(f)).Where(dto => dto is not null).Cast<MusicArtist>().ToArray()); + + var result = new Dictionary<string, MusicArtist[]>(artistNames.Count); + foreach (var name in artistNames) + { + if (lookup.TryGetValue(name, out var artistArray)) + { + result[name] = artistArray; + } + } + + return result; + } +} diff --git a/Jellyfin.Server.Implementations/Item/ChapterRepository.cs b/Jellyfin.Server.Implementations/Item/ChapterRepository.cs new file mode 100644 index 00000000..98700f32 --- /dev/null +++ b/Jellyfin.Server.Implementations/Item/ChapterRepository.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using Microsoft.EntityFrameworkCore; + +namespace Jellyfin.Server.Implementations.Item; + +/// <summary> +/// The Chapter manager. +/// </summary> +public class ChapterRepository : IChapterRepository +{ + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + private readonly IImageProcessor _imageProcessor; + + /// <summary> + /// Initializes a new instance of the <see cref="ChapterRepository"/> class. + /// </summary> + /// <param name="dbProvider">The EFCore provider.</param> + /// <param name="imageProcessor">The Image Processor.</param> + public ChapterRepository(IDbContextFactory<JellyfinDbContext> dbProvider, IImageProcessor imageProcessor) + { + _dbProvider = dbProvider; + _imageProcessor = imageProcessor; + } + + /// <inheritdoc /> + public ChapterInfo? GetChapter(Guid baseItemId, int index) + { + using var context = _dbProvider.CreateDbContext(); + var chapter = context.Chapters.AsNoTracking() + .Select(e => new + { + chapter = e, + baseItemPath = e.Item.Path + }) + .FirstOrDefault(e => e.chapter.ItemId.Equals(baseItemId) && e.chapter.ChapterIndex == index); + if (chapter is not null) + { + return Map(chapter.chapter, chapter.baseItemPath!); + } + + return null; + } + + /// <inheritdoc /> + public IReadOnlyList<ChapterInfo> GetChapters(Guid baseItemId) + { + using var context = _dbProvider.CreateDbContext(); + return context.Chapters.AsNoTracking().Where(e => e.ItemId.Equals(baseItemId)) + .Select(e => new + { + chapter = e, + baseItemPath = e.Item.Path + }) + .AsEnumerable() + .Select(e => Map(e.chapter, e.baseItemPath!)) + .ToArray(); + } + + /// <inheritdoc /> + public void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters) + { + using var context = _dbProvider.CreateDbContext(); + using (var transaction = context.Database.BeginTransaction()) + { + context.Chapters.Where(e => e.ItemId.Equals(itemId)).ExecuteDelete(); + for (var i = 0; i < chapters.Count; i++) + { + var chapter = chapters[i]; + context.Chapters.Add(Map(chapter, i, itemId)); + } + + context.SaveChanges(); + transaction.Commit(); + } + } + + /// <inheritdoc /> + public async Task DeleteChaptersAsync(Guid itemId, CancellationToken cancellationToken) + { + var dbContext = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + await dbContext.Chapters.Where(c => c.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false); + await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + } + } + + private Chapter Map(ChapterInfo chapterInfo, int index, Guid itemId) + { + return new Chapter() + { + ChapterIndex = index, + StartPositionTicks = chapterInfo.StartPositionTicks, + ImageDateModified = chapterInfo.ImageDateModified, + ImagePath = chapterInfo.ImagePath, + ItemId = itemId, + Name = chapterInfo.Name, + Item = null! + }; + } + + private ChapterInfo Map(Chapter chapterInfo, string baseItemPath) + { + var chapterEntity = new ChapterInfo() + { + StartPositionTicks = chapterInfo.StartPositionTicks, + ImageDateModified = chapterInfo.ImageDateModified.GetValueOrDefault(), + ImagePath = chapterInfo.ImagePath, + Name = chapterInfo.Name, + }; + + if (!string.IsNullOrEmpty(chapterInfo.ImagePath)) + { + chapterEntity.ImageTag = _imageProcessor.GetImageCacheTag(baseItemPath, chapterEntity.ImageDateModified); + } + + return chapterEntity; + } +} diff --git a/Jellyfin.Server.Implementations/Item/KeyframeRepository.cs b/Jellyfin.Server.Implementations/Item/KeyframeRepository.cs new file mode 100644 index 00000000..438458c6 --- /dev/null +++ b/Jellyfin.Server.Implementations/Item/KeyframeRepository.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Persistence; +using Microsoft.EntityFrameworkCore; + +namespace Jellyfin.Server.Implementations.Item; + +/// <summary> +/// Repository for obtaining Keyframe data. +/// </summary> +public class KeyframeRepository : IKeyframeRepository +{ + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + + /// <summary> + /// Initializes a new instance of the <see cref="KeyframeRepository"/> class. + /// </summary> + /// <param name="dbProvider">The EFCore db factory.</param> + public KeyframeRepository(IDbContextFactory<JellyfinDbContext> dbProvider) + { + _dbProvider = dbProvider; + } + + private static MediaEncoding.Keyframes.KeyframeData Map(KeyframeData entity) + { + return new MediaEncoding.Keyframes.KeyframeData( + entity.TotalDuration, + (entity.KeyframeTicks ?? []).ToList()); + } + + private KeyframeData Map(MediaEncoding.Keyframes.KeyframeData dto, Guid itemId) + { + return new() + { + ItemId = itemId, + TotalDuration = dto.TotalDuration, + KeyframeTicks = dto.KeyframeTicks.ToList() + }; + } + + /// <inheritdoc /> + public IReadOnlyList<MediaEncoding.Keyframes.KeyframeData> GetKeyframeData(Guid itemId) + { + using var context = _dbProvider.CreateDbContext(); + + return context.KeyframeData.AsNoTracking().Where(e => e.ItemId.Equals(itemId)).Select(e => Map(e)).ToList(); + } + + /// <inheritdoc /> + public async Task SaveKeyframeDataAsync(Guid itemId, MediaEncoding.Keyframes.KeyframeData data, CancellationToken cancellationToken) + { + using var context = _dbProvider.CreateDbContext(); + var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false); + await using (transaction.ConfigureAwait(false)) + { + await context.KeyframeData.Where(e => e.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false); + await context.KeyframeData.AddAsync(Map(data, itemId), cancellationToken).ConfigureAwait(false); + await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + await transaction.CommitAsync(cancellationToken).ConfigureAwait(false); + } + } + + /// <inheritdoc /> + public async Task DeleteKeyframeDataAsync(Guid itemId, CancellationToken cancellationToken) + { + using var context = _dbProvider.CreateDbContext(); + await context.KeyframeData.Where(e => e.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false); + await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + } +} diff --git a/Jellyfin.Server.Implementations/Item/MediaAttachmentRepository.cs b/Jellyfin.Server.Implementations/Item/MediaAttachmentRepository.cs new file mode 100644 index 00000000..e75dda43 --- /dev/null +++ b/Jellyfin.Server.Implementations/Item/MediaAttachmentRepository.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using Microsoft.EntityFrameworkCore; + +namespace Jellyfin.Server.Implementations.Item; + +/// <summary> +/// Manager for handling Media Attachments. +/// </summary> +/// <param name="dbProvider">Efcore Factory.</param> +public class MediaAttachmentRepository(IDbContextFactory<JellyfinDbContext> dbProvider) : IMediaAttachmentRepository +{ + /// <inheritdoc /> + public void SaveMediaAttachments( + Guid id, + IReadOnlyList<MediaAttachment> attachments, + CancellationToken cancellationToken) + { + using var context = dbProvider.CreateDbContext(); + using var transaction = context.Database.BeginTransaction(); + + // Users may replace a media with a version that includes attachments to one without them. + // So when saving attachments is triggered by a library scan, we always unconditionally + // clear the old ones, and then add the new ones if given. + context.AttachmentStreamInfos.Where(e => e.ItemId.Equals(id)).ExecuteDelete(); + if (attachments.Any()) + { + context.AttachmentStreamInfos.AddRange(attachments.Select(e => Map(e, id))); + } + + context.SaveChanges(); + transaction.Commit(); + } + + /// <inheritdoc /> + public IReadOnlyList<MediaAttachment> GetMediaAttachments(MediaAttachmentQuery filter) + { + using var context = dbProvider.CreateDbContext(); + var query = context.AttachmentStreamInfos.AsNoTracking().Where(e => e.ItemId.Equals(filter.ItemId)); + if (filter.Index.HasValue) + { + query = query.Where(e => e.Index == filter.Index); + } + + return query.AsEnumerable().Select(Map).ToArray(); + } + + private MediaAttachment Map(AttachmentStreamInfo attachment) + { + return new MediaAttachment() + { + Codec = attachment.Codec, + CodecTag = attachment.CodecTag, + Comment = attachment.Comment, + FileName = attachment.Filename, + Index = attachment.Index, + MimeType = attachment.MimeType, + }; + } + + private AttachmentStreamInfo Map(MediaAttachment attachment, Guid id) + { + return new AttachmentStreamInfo() + { + Codec = attachment.Codec, + CodecTag = attachment.CodecTag, + Comment = attachment.Comment, + Filename = attachment.FileName, + Index = attachment.Index, + MimeType = attachment.MimeType, + ItemId = id, + Item = null! + }; + } +} diff --git a/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs b/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs new file mode 100644 index 00000000..64874cca --- /dev/null +++ b/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using Microsoft.EntityFrameworkCore; + +namespace Jellyfin.Server.Implementations.Item; + +/// <summary> +/// Repository for obtaining MediaStreams. +/// </summary> +public class MediaStreamRepository : IMediaStreamRepository +{ + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + private readonly IServerApplicationHost _serverApplicationHost; + private readonly ILocalizationManager _localization; + + /// <summary> + /// Initializes a new instance of the <see cref="MediaStreamRepository"/> class. + /// </summary> + /// <param name="dbProvider">The EFCore db factory.</param> + /// <param name="serverApplicationHost">The Application host.</param> + /// <param name="localization">The Localisation Provider.</param> + public MediaStreamRepository(IDbContextFactory<JellyfinDbContext> dbProvider, IServerApplicationHost serverApplicationHost, ILocalizationManager localization) + { + _dbProvider = dbProvider; + _serverApplicationHost = serverApplicationHost; + _localization = localization; + } + + /// <inheritdoc /> + public void SaveMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken) + { + using var context = _dbProvider.CreateDbContext(); + using var transaction = context.Database.BeginTransaction(); + + context.MediaStreamInfos.Where(e => e.ItemId.Equals(id)).ExecuteDelete(); + context.MediaStreamInfos.AddRange(streams.Select(f => Map(f, id))); + context.SaveChanges(); + + transaction.Commit(); + } + + /// <inheritdoc /> + public IReadOnlyList<MediaStream> GetMediaStreams(MediaStreamQuery filter) + { + using var context = _dbProvider.CreateDbContext(); + return TranslateQuery(context.MediaStreamInfos.AsNoTracking(), filter).AsEnumerable().Select(Map).ToArray(); + } + + private string? GetPathToSave(string? path) + { + if (path is null) + { + return null; + } + + return _serverApplicationHost.ReverseVirtualPath(path); + } + + private string? RestorePath(string? path) + { + if (path is null) + { + return null; + } + + return _serverApplicationHost.ExpandVirtualPath(path); + } + + private IQueryable<MediaStreamInfo> TranslateQuery(IQueryable<MediaStreamInfo> query, MediaStreamQuery filter) + { + query = query.Where(e => e.ItemId.Equals(filter.ItemId)); + if (filter.Index.HasValue) + { + query = query.Where(e => e.StreamIndex == filter.Index); + } + + if (filter.Type.HasValue) + { + var typeValue = (MediaStreamTypeEntity)filter.Type.Value; + query = query.Where(e => e.StreamType == typeValue); + } + + return query.OrderBy(e => e.StreamIndex); + } + + private MediaStream Map(MediaStreamInfo entity) + { + var dto = new MediaStream(); + dto.Index = entity.StreamIndex; + dto.Type = (MediaStreamType)entity.StreamType; + + dto.IsAVC = entity.IsAvc; + dto.Codec = entity.Codec; + + var language = entity.Language; + + // Check if the language has multiple three letter ISO codes + // if yes choose the first as that is the ISO 639-2/T code we're needing + if (language != null && _localization.TryGetISO6392TFromB(language, out string? isoT)) + { + language = isoT; + } + + dto.Language = language; + + dto.ChannelLayout = entity.ChannelLayout; + dto.Profile = entity.Profile; + dto.AspectRatio = entity.AspectRatio; + dto.Path = RestorePath(entity.Path); + dto.IsInterlaced = entity.IsInterlaced.GetValueOrDefault(); + dto.BitRate = entity.BitRate; + dto.Channels = entity.Channels; + dto.SampleRate = entity.SampleRate; + dto.IsDefault = entity.IsDefault; + dto.IsForced = entity.IsForced; + dto.IsExternal = entity.IsExternal; + dto.Height = entity.Height; + dto.Width = entity.Width; + dto.AverageFrameRate = entity.AverageFrameRate; + dto.RealFrameRate = entity.RealFrameRate; + dto.Level = entity.Level; + dto.PixelFormat = entity.PixelFormat; + dto.BitDepth = entity.BitDepth; + dto.IsAnamorphic = entity.IsAnamorphic; + dto.RefFrames = entity.RefFrames; + dto.CodecTag = entity.CodecTag; + dto.Comment = entity.Comment; + dto.NalLengthSize = entity.NalLengthSize; + dto.Title = entity.Title; + dto.TimeBase = entity.TimeBase; + dto.CodecTimeBase = entity.CodecTimeBase; + dto.ColorPrimaries = entity.ColorPrimaries; + dto.ColorSpace = entity.ColorSpace; + dto.ColorTransfer = entity.ColorTransfer; + dto.DvVersionMajor = entity.DvVersionMajor; + dto.DvVersionMinor = entity.DvVersionMinor; + dto.DvProfile = entity.DvProfile; + dto.DvLevel = entity.DvLevel; + dto.RpuPresentFlag = entity.RpuPresentFlag; + dto.ElPresentFlag = entity.ElPresentFlag; + dto.BlPresentFlag = entity.BlPresentFlag; + dto.DvBlSignalCompatibilityId = entity.DvBlSignalCompatibilityId; + dto.IsHearingImpaired = entity.IsHearingImpaired.GetValueOrDefault(); + dto.Rotation = entity.Rotation; + dto.Hdr10PlusPresentFlag = entity.Hdr10PlusPresentFlag; + + if (dto.Type is MediaStreamType.Audio or MediaStreamType.Subtitle) + { + dto.LocalizedDefault = _localization.GetLocalizedString("Default"); + dto.LocalizedExternal = _localization.GetLocalizedString("External"); + + if (!string.IsNullOrEmpty(dto.Language)) + { + var culture = _localization.FindLanguageInfo(dto.Language); + dto.LocalizedLanguage = culture?.DisplayName; + } + + if (dto.Type is MediaStreamType.Subtitle) + { + dto.LocalizedUndefined = _localization.GetLocalizedString("Undefined"); + dto.LocalizedForced = _localization.GetLocalizedString("Forced"); + dto.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired"); + } + } + + return dto; + } + + private MediaStreamInfo Map(MediaStream dto, Guid itemId) + { + var entity = new MediaStreamInfo + { + Item = null!, + ItemId = itemId, + StreamIndex = dto.Index, + StreamType = (MediaStreamTypeEntity)dto.Type, + IsAvc = dto.IsAVC, + + Codec = dto.Codec, + Language = dto.Language, + ChannelLayout = dto.ChannelLayout, + Profile = dto.Profile, + AspectRatio = dto.AspectRatio, + Path = GetPathToSave(dto.Path) ?? dto.Path, + IsInterlaced = dto.IsInterlaced, + BitRate = dto.BitRate, + Channels = dto.Channels, + SampleRate = dto.SampleRate, + IsDefault = dto.IsDefault, + IsForced = dto.IsForced, + IsExternal = dto.IsExternal, + Height = dto.Height, + Width = dto.Width, + AverageFrameRate = dto.AverageFrameRate, + RealFrameRate = dto.RealFrameRate, + Level = dto.Level.HasValue ? (float)dto.Level : null, + PixelFormat = dto.PixelFormat, + BitDepth = dto.BitDepth, + IsAnamorphic = dto.IsAnamorphic, + RefFrames = dto.RefFrames, + CodecTag = dto.CodecTag, + Comment = dto.Comment, + NalLengthSize = dto.NalLengthSize, + Title = dto.Title, + TimeBase = dto.TimeBase, + CodecTimeBase = dto.CodecTimeBase, + ColorPrimaries = dto.ColorPrimaries, + ColorSpace = dto.ColorSpace, + ColorTransfer = dto.ColorTransfer, + DvVersionMajor = dto.DvVersionMajor, + DvVersionMinor = dto.DvVersionMinor, + DvProfile = dto.DvProfile, + DvLevel = dto.DvLevel, + RpuPresentFlag = dto.RpuPresentFlag, + ElPresentFlag = dto.ElPresentFlag, + BlPresentFlag = dto.BlPresentFlag, + DvBlSignalCompatibilityId = dto.DvBlSignalCompatibilityId, + IsHearingImpaired = dto.IsHearingImpaired, + Rotation = dto.Rotation, + Hdr10PlusPresentFlag = dto.Hdr10PlusPresentFlag, + }; + return entity; + } +} diff --git a/Jellyfin.Server.Implementations/Item/OrderMapper.cs b/Jellyfin.Server.Implementations/Item/OrderMapper.cs new file mode 100644 index 00000000..1ae7cc6c --- /dev/null +++ b/Jellyfin.Server.Implementations/Item/OrderMapper.cs @@ -0,0 +1,98 @@ +#pragma warning disable RS0030 // Do not use banned APIs + +using System; +using System.Linq; +using System.Linq.Expressions; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Entities; +using Microsoft.EntityFrameworkCore; + +namespace Jellyfin.Server.Implementations.Item; + +/// <summary> +/// Static class for methods which maps types of ordering to their respecting ordering functions. +/// </summary> +public static class OrderMapper +{ + /// <summary> + /// Creates Func to be executed later with a given BaseItemEntity input for sorting items on query. + /// </summary> + /// <param name="sortBy">Item property to sort by.</param> + /// <param name="query">Context Query.</param> + /// <param name="jellyfinDbContext">Context.</param> + /// <returns>Func to be executed later for sorting query.</returns> + public static Expression<Func<BaseItemEntity, object?>> MapOrderByField(ItemSortBy sortBy, InternalItemsQuery query, JellyfinDbContext jellyfinDbContext) + { + return (sortBy, query.User) switch + { + (ItemSortBy.AirTime, _) => e => e.SortName, // TODO + (ItemSortBy.Runtime, _) => e => e.RunTimeTicks, + (ItemSortBy.Random, _) => e => EF.Functions.Random(), + (ItemSortBy.DatePlayed, _) => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.LastPlayedDate, + (ItemSortBy.PlayCount, _) => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.PlayCount, + (ItemSortBy.IsFavoriteOrLiked, _) => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.IsFavorite, + (ItemSortBy.IsFolder, _) => e => e.IsFolder, + (ItemSortBy.IsPlayed, _) => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.Played, + (ItemSortBy.IsUnplayed, _) => e => !e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.Played, + (ItemSortBy.DateLastContentAdded, _) => e => e.DateLastMediaAdded, + (ItemSortBy.Artist, _) => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Artist).Select(f => f.ItemValue.CleanValue).FirstOrDefault(), + (ItemSortBy.AlbumArtist, _) => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.AlbumArtist).Select(f => f.ItemValue.CleanValue).FirstOrDefault(), + (ItemSortBy.Studio, _) => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Studios).Select(f => f.ItemValue.CleanValue).FirstOrDefault(), + (ItemSortBy.OfficialRating, _) => e => e.InheritedParentalRatingValue, + (ItemSortBy.SeriesSortName, _) => e => e.SeriesName, + (ItemSortBy.Album, _) => e => e.Album, + (ItemSortBy.DateCreated, _) => e => e.DateCreated, + (ItemSortBy.PremiereDate, _) => e => (e.PremiereDate ?? (e.ProductionYear.HasValue ? DateTime.MinValue.AddYears(e.ProductionYear.Value - 1) : null)), + (ItemSortBy.StartDate, _) => e => e.StartDate, + (ItemSortBy.Name, _) => e => e.CleanName, + (ItemSortBy.CommunityRating, _) => e => e.CommunityRating, + (ItemSortBy.ProductionYear, _) => e => e.ProductionYear, + (ItemSortBy.CriticRating, _) => e => e.CriticRating, + (ItemSortBy.VideoBitRate, _) => e => e.TotalBitrate, + (ItemSortBy.ParentIndexNumber, _) => e => e.ParentIndexNumber, + (ItemSortBy.IndexNumber, _) => e => e.IndexNumber, + (ItemSortBy.SeriesDatePlayed, not null) => e => + jellyfinDbContext.BaseItems + .Where(w => w.SeriesPresentationUniqueKey == e.PresentationUniqueKey) + .Join(jellyfinDbContext.UserData.Where(w => w.UserId == query.User.Id && w.Played), f => f.Id, f => f.ItemId, (item, userData) => userData.LastPlayedDate) + .Max(f => f), + (ItemSortBy.SeriesDatePlayed, null) => e => jellyfinDbContext.BaseItems.Where(w => w.SeriesPresentationUniqueKey == e.PresentationUniqueKey) + .Join(jellyfinDbContext.UserData.Where(w => w.Played), f => f.Id, f => f.ItemId, (item, userData) => userData.LastPlayedDate) + .Max(f => f), + // ItemSortBy.SeriesDatePlayed => e => jellyfinDbContext.UserData + // .Where(u => u.Item!.SeriesPresentationUniqueKey == e.PresentationUniqueKey && u.Played) + // .Max(f => f.LastPlayedDate), + // ItemSortBy.AiredEpisodeOrder => "AiredEpisodeOrder", + _ => e => e.SortName + }; + } + + /// <summary> + /// Creates an expression to order search results by match quality. + /// Prioritizes: exact match (0) > prefix match with word boundary (1) > prefix match (2) > contains (3). + /// </summary> + /// <param name="searchTerm">The search term to match against.</param> + /// <returns>An expression that returns an integer representing match quality (lower is better).</returns> + public static Expression<Func<BaseItemEntity, int>> MapSearchRelevanceOrder(string searchTerm) + { + var cleanSearchTerm = GetCleanValue(searchTerm); + var searchPrefix = cleanSearchTerm + " "; + return e => + e.CleanName == cleanSearchTerm ? 0 : + e.CleanName!.StartsWith(searchPrefix) ? 1 : + e.CleanName!.StartsWith(cleanSearchTerm) ? 2 : 3; + } + + private static string GetCleanValue(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return value; + } + + return value.RemoveDiacritics().ToLowerInvariant(); + } +} diff --git a/Jellyfin.Server.Implementations/Item/PeopleRepository.cs b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs new file mode 100644 index 00000000..e2569241 --- /dev/null +++ b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Entities.Libraries; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; +using Microsoft.EntityFrameworkCore; + +namespace Jellyfin.Server.Implementations.Item; +#pragma warning disable RS0030 // Do not use banned APIs +#pragma warning disable CA1304 // Specify CultureInfo +#pragma warning disable CA1311 // Specify a culture or use an invariant version +#pragma warning disable CA1862 // Use the 'StringComparison' method overloads to perform case-insensitive string comparisons + +/// <summary> +/// Manager for handling people. +/// </summary> +/// <param name="dbProvider">Efcore Factory.</param> +/// <param name="itemTypeLookup">Items lookup service.</param> +/// <remarks> +/// Initializes a new instance of the <see cref="PeopleRepository"/> class. +/// </remarks> +public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, IItemTypeLookup itemTypeLookup) : IPeopleRepository +{ + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider = dbProvider; + + /// <inheritdoc/> + public IReadOnlyList<PersonInfo> GetPeople(InternalPeopleQuery filter) + { + using var context = _dbProvider.CreateDbContext(); + var dbQuery = TranslateQuery(context.Peoples.AsNoTracking(), context, filter); + + // Include PeopleBaseItemMap + if (!filter.ItemId.IsEmpty()) + { + dbQuery = dbQuery.Include(p => p.BaseItems!.Where(m => m.ItemId == filter.ItemId)) + .OrderBy(e => e.BaseItems!.First(e => e.ItemId == filter.ItemId).ListOrder) + .ThenBy(e => e.PersonType) + .ThenBy(e => e.Name); + } + else + { + dbQuery = dbQuery.OrderBy(e => e.Name); + } + + if (filter.Limit > 0) + { + dbQuery = dbQuery.Take(filter.Limit); + } + + return dbQuery.AsEnumerable().Select(Map).ToArray(); + } + + /// <inheritdoc/> + public IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery filter) + { + using var context = _dbProvider.CreateDbContext(); + var dbQuery = TranslateQuery(context.Peoples.AsNoTracking(), context, filter).Select(e => e.Name).Distinct(); + + // dbQuery = dbQuery.OrderBy(e => e.ListOrder); + if (filter.Limit > 0) + { + dbQuery = dbQuery.Take(filter.Limit); + } + + return dbQuery.ToArray(); + } + + /// <inheritdoc /> + public void UpdatePeople(Guid itemId, IReadOnlyList<PersonInfo> people) + { + foreach (var person in people) + { + person.Name = person.Name.Trim(); + person.Role = person.Role?.Trim() ?? string.Empty; + } + + // multiple metadata providers can provide the _same_ person + people = people.DistinctBy(e => e.Name + "-" + e.Type).ToArray(); + var personKeys = people.Select(e => e.Name + "-" + e.Type).ToArray(); + + using var context = _dbProvider.CreateDbContext(); + using var transaction = context.Database.BeginTransaction(); + var existingPersons = context.Peoples.Select(e => new + { + item = e, + SelectionKey = e.Name + "-" + e.PersonType + }) + .Where(p => personKeys.Contains(p.SelectionKey)) + .Select(f => f.item) + .ToArray(); + + var toAdd = people + .Where(e => e.Type is not PersonKind.Artist && e.Type is not PersonKind.AlbumArtist) + .Where(e => !existingPersons.Any(f => f.Name == e.Name && f.PersonType == e.Type.ToString())) + .Select(Map); + context.Peoples.AddRange(toAdd); + context.SaveChanges(); + + var personsEntities = toAdd.Concat(existingPersons).ToArray(); + + var existingMaps = context.PeopleBaseItemMap.Include(e => e.People).Where(e => e.ItemId == itemId).ToList(); + + var listOrder = 0; + + foreach (var person in people) + { + if (person.Type == PersonKind.Artist || person.Type == PersonKind.AlbumArtist) + { + continue; + } + + var entityPerson = personsEntities.First(e => e.Name == person.Name && e.PersonType == person.Type.ToString()); + var existingMap = existingMaps.FirstOrDefault(e => e.People.Name == person.Name && e.People.PersonType == person.Type.ToString() && e.Role == person.Role); + if (existingMap is null) + { + context.PeopleBaseItemMap.Add(new PeopleBaseItemMap() + { + Item = null!, + ItemId = itemId, + People = null!, + PeopleId = entityPerson.Id, + ListOrder = listOrder, + SortOrder = person.SortOrder, + Role = person.Role + }); + } + else + { + // Update the order for existing mappings + existingMap.ListOrder = listOrder; + existingMap.SortOrder = person.SortOrder; + // person mapping already exists so remove from list + existingMaps.Remove(existingMap); + } + + listOrder++; + } + + context.PeopleBaseItemMap.RemoveRange(existingMaps); + + context.SaveChanges(); + transaction.Commit(); + } + + private PersonInfo Map(People people) + { + var mapping = people.BaseItems?.FirstOrDefault(); + var personInfo = new PersonInfo() + { + Id = people.Id, + Name = people.Name, + Role = mapping?.Role, + SortOrder = mapping?.SortOrder + }; + if (Enum.TryParse<PersonKind>(people.PersonType, out var kind)) + { + personInfo.Type = kind; + } + + return personInfo; + } + + private People Map(PersonInfo people) + { + var personInfo = new People() + { + Name = people.Name, + PersonType = people.Type.ToString(), + Id = people.Id, + }; + + return personInfo; + } + + private IQueryable<People> TranslateQuery(IQueryable<People> query, JellyfinDbContext context, InternalPeopleQuery filter) + { + if (filter.User is not null && filter.IsFavorite.HasValue) + { + var personType = itemTypeLookup.BaseItemKindNames[BaseItemKind.Person]; + var oldQuery = query; + + query = context.UserData + .Where(u => u.Item!.Type == personType && u.IsFavorite == filter.IsFavorite && u.UserId.Equals(filter.User.Id)) + .Join(oldQuery, e => e.Item!.Name, e => e.Name, (item, person) => person) + .Distinct() + .AsNoTracking(); + } + + if (!filter.ItemId.IsEmpty()) + { + query = query.Where(e => e.BaseItems!.Any(w => w.ItemId.Equals(filter.ItemId))); + } + + if (!filter.AppearsInItemId.IsEmpty()) + { + query = query.Where(e => e.BaseItems!.Any(w => w.ItemId.Equals(filter.AppearsInItemId))); + } + + var queryPersonTypes = filter.PersonTypes.Where(IsValidPersonType).ToList(); + if (queryPersonTypes.Count > 0) + { + query = query.Where(e => queryPersonTypes.Contains(e.PersonType)); + } + + var queryExcludePersonTypes = filter.ExcludePersonTypes.Where(IsValidPersonType).ToList(); + + if (queryExcludePersonTypes.Count > 0) + { + query = query.Where(e => !queryPersonTypes.Contains(e.PersonType)); + } + + if (filter.MaxListOrder.HasValue && !filter.ItemId.IsEmpty()) + { + query = query.Where(e => e.BaseItems!.First(w => w.ItemId == filter.ItemId).ListOrder <= filter.MaxListOrder.Value); + } + + if (!string.IsNullOrWhiteSpace(filter.NameContains)) + { + var nameContainsUpper = filter.NameContains.ToUpper(); + query = query.Where(e => e.Name.ToUpper().Contains(nameContainsUpper)); + } + + return query; + } + + private bool IsAlphaNumeric(string str) + { + if (string.IsNullOrWhiteSpace(str)) + { + return false; + } + + for (int i = 0; i < str.Length; i++) + { + if (!char.IsLetter(str[i]) && !char.IsNumber(str[i])) + { + return false; + } + } + + return true; + } + + private bool IsValidPersonType(string value) + { + return IsAlphaNumeric(value); + } +} diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj new file mode 100644 index 00000000..4f0c3772 --- /dev/null +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -0,0 +1,41 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net10.0</TargetFramework> + <GenerateAssemblyInfo>false</GenerateAssemblyInfo> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + </PropertyGroup> + + <!-- Code Analyzers --> + <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="IDisposableAnalyzers"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> + <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> + </ItemGroup> + + <ItemGroup> + <Compile Include="..\SharedVersion.cs" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="AsyncKeyedLock" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Jellyfin.Data\Jellyfin.Data.csproj" /> + <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> + <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> + <ProjectReference Include="..\src\Jellyfin.Database\Jellyfin.Database.Implementations\Jellyfin.Database.Implementations.csproj" /> + <ProjectReference Include="..\src\Jellyfin.Database\Jellyfin.Database.Providers.Sqlite\Jellyfin.Database.Providers.Sqlite.csproj" /> + </ItemGroup> + +</Project> diff --git a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs new file mode 100644 index 00000000..d00c8746 --- /dev/null +++ b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs @@ -0,0 +1,289 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.MediaSegments; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.MediaSegments; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.MediaSegments; + +/// <summary> +/// Manages media segments retrieval and storage. +/// </summary> +public class MediaSegmentManager : IMediaSegmentManager +{ + private readonly ILogger<MediaSegmentManager> _logger; + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + private readonly IMediaSegmentProvider[] _segmentProviders; + + /// <summary> + /// Initializes a new instance of the <see cref="MediaSegmentManager"/> class. + /// </summary> + /// <param name="logger">Logger.</param> + /// <param name="dbProvider">EFCore Database factory.</param> + /// <param name="segmentProviders">List of all media segment providers.</param> + public MediaSegmentManager( + ILogger<MediaSegmentManager> logger, + IDbContextFactory<JellyfinDbContext> dbProvider, + IEnumerable<IMediaSegmentProvider> segmentProviders) + { + _logger = logger; + _dbProvider = dbProvider; + + _segmentProviders = segmentProviders + .OrderBy(i => i is IHasOrder hasOrder ? hasOrder.Order : 0) + .ToArray(); + } + + /// <inheritdoc/> + public async Task RunSegmentPluginProviders(BaseItem baseItem, LibraryOptions libraryOptions, bool forceOverwrite, CancellationToken cancellationToken) + { + var providers = _segmentProviders + .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name))) + .OrderBy(i => + { + var index = libraryOptions.MediaSegmentProviderOrder.IndexOf(i.Name); + return index == -1 ? int.MaxValue : index; + }) + .ToList(); + + if (providers.Count == 0) + { + _logger.LogDebug("Skipping media segment extraction as no providers are enabled for {MediaPath}", baseItem.Path); + return; + } + + var db = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + await using (db.ConfigureAwait(false)) + { + _logger.LogDebug("Start media segment extraction for {MediaPath} with {CountProviders} providers enabled", baseItem.Path, providers.Count); + + if (forceOverwrite) + { + // delete all existing media segments if forceOverwrite is set. + await db.MediaSegments.Where(e => e.ItemId.Equals(baseItem.Id)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false); + } + + foreach (var provider in providers) + { + if (!await provider.Supports(baseItem).ConfigureAwait(false)) + { + _logger.LogDebug("Media Segment provider {ProviderName} does not support item with path {MediaPath}", provider.Name, baseItem.Path); + continue; + } + + IQueryable<MediaSegment> existingSegments; + if (forceOverwrite) + { + existingSegments = Array.Empty<MediaSegment>().AsQueryable(); + } + else + { + existingSegments = db.MediaSegments.Where(e => e.ItemId.Equals(baseItem.Id) && e.SegmentProviderId == GetProviderId(provider.Name)); + } + + var requestItem = new MediaSegmentGenerationRequest() + { + ItemId = baseItem.Id, + ExistingSegments = existingSegments.Select(e => Map(e)).ToArray() + }; + + try + { + var segments = await provider.GetMediaSegments(requestItem, cancellationToken) + .ConfigureAwait(false); + + if (!forceOverwrite) + { + var existingSegmentsList = existingSegments.ToArray(); // Cannot use requestItem's list, as the provider might tamper with its items. + if (segments.Count == requestItem.ExistingSegments.Count && segments.All(e => existingSegmentsList.Any(f => + { + return + e.StartTicks == f.StartTicks && + e.EndTicks == f.EndTicks && + e.Type == f.Type; + }))) + { + _logger.LogDebug("Media Segment provider {ProviderName} did not modify any segments for {MediaPath}", provider.Name, baseItem.Path); + continue; + } + + // delete existing media segments that were re-generated. + await existingSegments.ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false); + } + + if (segments.Count == 0 && !requestItem.ExistingSegments.Any()) + { + _logger.LogDebug("Media Segment provider {ProviderName} did not find any segments for {MediaPath}", provider.Name, baseItem.Path); + continue; + } + else if (segments.Count == 0 && requestItem.ExistingSegments.Any()) + { + _logger.LogDebug("Media Segment provider {ProviderName} deleted all segments for {MediaPath}", provider.Name, baseItem.Path); + continue; + } + + _logger.LogInformation("Media Segment provider {ProviderName} found {CountSegments} for {MediaPath}", provider.Name, segments.Count, baseItem.Path); + var providerId = GetProviderId(provider.Name); + foreach (var segment in segments) + { + segment.ItemId = baseItem.Id; + await CreateSegmentAsync(segment, providerId).ConfigureAwait(false); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Provider {ProviderName} failed to extract segments from {MediaPath}", provider.Name, baseItem.Path); + } + } + } + } + + /// <inheritdoc /> + public async Task<MediaSegmentDto> CreateSegmentAsync(MediaSegmentDto mediaSegment, string segmentProviderId) + { + ArgumentOutOfRangeException.ThrowIfLessThan(mediaSegment.EndTicks, mediaSegment.StartTicks); + + var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (db.ConfigureAwait(false)) + { + db.MediaSegments.Add(Map(mediaSegment, segmentProviderId)); + await db.SaveChangesAsync().ConfigureAwait(false); + } + + return mediaSegment; + } + + /// <inheritdoc /> + public async Task DeleteSegmentAsync(Guid segmentId) + { + var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (db.ConfigureAwait(false)) + { + await db.MediaSegments.Where(e => e.Id.Equals(segmentId)).ExecuteDeleteAsync().ConfigureAwait(false); + } + } + + /// <inheritdoc /> + public async Task DeleteSegmentsAsync(Guid itemId, CancellationToken cancellationToken) + { + var db = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + await using (db.ConfigureAwait(false)) + { + await db.MediaSegments.Where(e => e.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false); + } + } + + /// <inheritdoc /> + public async Task<IEnumerable<MediaSegmentDto>> GetSegmentsAsync(BaseItem? item, IEnumerable<MediaSegmentType>? typeFilter, LibraryOptions libraryOptions, bool filterByProvider = true) + { + if (item is null) + { + _logger.LogError("Tried to request segments for an invalid item"); + return []; + } + + var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (db.ConfigureAwait(false)) + { + var query = db.MediaSegments + .Where(e => e.ItemId.Equals(item.Id)); + + if (typeFilter is not null) + { + query = query.Where(e => typeFilter.Contains(e.Type)); + } + + if (filterByProvider) + { + var providerIds = _segmentProviders + .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name))) + .Select(f => GetProviderId(f.Name)) + .ToArray(); + if (providerIds.Length == 0) + { + return []; + } + + query = query.Where(e => providerIds.Contains(e.SegmentProviderId)); + } + + return query + .OrderBy(e => e.StartTicks) + .AsNoTracking() + .AsEnumerable() + .Select(Map) + .ToArray(); + } + } + + private static MediaSegmentDto Map(MediaSegment segment) + { + return new MediaSegmentDto() + { + Id = segment.Id, + EndTicks = segment.EndTicks, + ItemId = segment.ItemId, + StartTicks = segment.StartTicks, + Type = segment.Type + }; + } + + private static MediaSegment Map(MediaSegmentDto segment, string segmentProviderId) + { + return new MediaSegment() + { + Id = segment.Id, + EndTicks = segment.EndTicks, + ItemId = segment.ItemId, + StartTicks = segment.StartTicks, + Type = segment.Type, + SegmentProviderId = segmentProviderId + }; + } + + /// <inheritdoc /> + public bool HasSegments(Guid itemId) + { + using var db = _dbProvider.CreateDbContext(); + return db.MediaSegments.Any(e => e.ItemId.Equals(itemId)); + } + + /// <inheritdoc/> + public bool IsTypeSupported(BaseItem baseItem) + { + return baseItem.MediaType is Data.Enums.MediaType.Video or Data.Enums.MediaType.Audio; + } + + /// <inheritdoc/> + public IEnumerable<(string Name, string Id)> GetSupportedProviders(BaseItem item) + { + if (item is not (Video or Audio)) + { + return []; + } + + return _segmentProviders + .Select(p => (p.Name, GetProviderId(p.Name))); + } + + private string GetProviderId(string name) + => name.ToLowerInvariant() + .GetMD5() + .ToString("N", CultureInfo.InvariantCulture); +} diff --git a/Jellyfin.Server.Implementations/Properties/AssemblyInfo.cs b/Jellyfin.Server.Implementations/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..6528d4fd --- /dev/null +++ b/Jellyfin.Server.Implementations/Properties/AssemblyInfo.cs @@ -0,0 +1,23 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Jellyfin.Server.Implementations")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Jellyfin Project")] +[assembly: AssemblyProduct("Jellyfin Server")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] +[assembly: InternalsVisibleTo("Jellyfin.Server.Implementations.Tests")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] diff --git a/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs b/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs new file mode 100644 index 00000000..cf029346 --- /dev/null +++ b/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities.Security; +using MediaBrowser.Controller.Security; +using Microsoft.EntityFrameworkCore; + +namespace Jellyfin.Server.Implementations.Security +{ + /// <inheritdoc /> + public class AuthenticationManager : IAuthenticationManager + { + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + + /// <summary> + /// Initializes a new instance of the <see cref="AuthenticationManager"/> class. + /// </summary> + /// <param name="dbProvider">The database provider.</param> + public AuthenticationManager(IDbContextFactory<JellyfinDbContext> dbProvider) + { + _dbProvider = dbProvider; + } + + /// <inheritdoc /> + public async Task CreateApiKey(string name) + { + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + dbContext.ApiKeys.Add(new ApiKey(name)); + + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + } + + /// <inheritdoc /> + public async Task<IReadOnlyList<AuthenticationInfo>> GetApiKeys() + { + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + return await dbContext.ApiKeys + .Select(key => new AuthenticationInfo + { + AppName = key.Name, + AccessToken = key.AccessToken, + DateCreated = key.DateCreated, + DeviceId = string.Empty, + DeviceName = string.Empty, + AppVersion = string.Empty + }).ToListAsync().ConfigureAwait(false); + } + } + + /// <inheritdoc /> + public async Task DeleteApiKey(string accessToken) + { + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + await dbContext.ApiKeys + .Where(apiKey => apiKey.AccessToken == accessToken) + .ExecuteDeleteAsync() + .ConfigureAwait(false); + } + } + } +} diff --git a/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs new file mode 100644 index 00000000..e3fe517c --- /dev/null +++ b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs @@ -0,0 +1,319 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using Jellyfin.Data.Queries; +using Jellyfin.Database.Implementations; +using Jellyfin.Extensions; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; +using Microsoft.Net.Http.Headers; + +namespace Jellyfin.Server.Implementations.Security +{ + public class AuthorizationContext : IAuthorizationContext + { + private readonly IDbContextFactory<JellyfinDbContext> _jellyfinDbProvider; + private readonly IUserManager _userManager; + private readonly IDeviceManager _deviceManager; + private readonly IServerApplicationHost _serverApplicationHost; + private readonly IServerConfigurationManager _configurationManager; + + public AuthorizationContext( + IDbContextFactory<JellyfinDbContext> jellyfinDb, + IUserManager userManager, + IDeviceManager deviceManager, + IServerApplicationHost serverApplicationHost, + IServerConfigurationManager configurationManager) + { + _jellyfinDbProvider = jellyfinDb; + _userManager = userManager; + _deviceManager = deviceManager; + _serverApplicationHost = serverApplicationHost; + _configurationManager = configurationManager; + } + + public Task<AuthorizationInfo> GetAuthorizationInfo(HttpContext requestContext) + { + if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached) && cached is not null) + { + return Task.FromResult((AuthorizationInfo)cached); // Cache should never contain null + } + + return GetAuthorization(requestContext); + } + + public async Task<AuthorizationInfo> GetAuthorizationInfo(HttpRequest requestContext) + { + var auth = GetAuthorizationDictionary(requestContext); + var authInfo = await GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query).ConfigureAwait(false); + return authInfo; + } + + /// <summary> + /// Gets the authorization. + /// </summary> + /// <param name="httpContext">The HTTP context.</param> + /// <returns>Dictionary{System.StringSystem.String}.</returns> + private async Task<AuthorizationInfo> GetAuthorization(HttpContext httpContext) + { + var authInfo = await GetAuthorizationInfo(httpContext.Request).ConfigureAwait(false); + + httpContext.Request.HttpContext.Items["AuthorizationInfo"] = authInfo; + return authInfo; + } + + private async Task<AuthorizationInfo> GetAuthorizationInfoFromDictionary( + Dictionary<string, string>? auth, + IHeaderDictionary headers, + IQueryCollection queryString) + { + string? deviceId = null; + string? deviceName = null; + string? client = null; + string? version = null; + string? token = null; + + if (auth is not null) + { + auth.TryGetValue("DeviceId", out deviceId); + auth.TryGetValue("Device", out deviceName); + auth.TryGetValue("Client", out client); + auth.TryGetValue("Version", out version); + auth.TryGetValue("Token", out token); + } + + if (_configurationManager.Configuration.EnableLegacyAuthorization && string.IsNullOrEmpty(token)) + { + token = headers["X-Emby-Token"]; + } + + if (_configurationManager.Configuration.EnableLegacyAuthorization && string.IsNullOrEmpty(token)) + { + token = headers["X-MediaBrowser-Token"]; + } + + if (string.IsNullOrEmpty(token)) + { + token = queryString["ApiKey"]; + } + + if (_configurationManager.Configuration.EnableLegacyAuthorization && string.IsNullOrEmpty(token)) + { + token = queryString["api_key"]; + } + + var authInfo = new AuthorizationInfo + { + Client = client, + Device = deviceName, + DeviceId = deviceId, + Version = version, + Token = token, + IsAuthenticated = false + }; + + if (!authInfo.HasToken) + { + // Request doesn't contain a token. + return authInfo; + } + + var dbContext = await _jellyfinDbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + var device = _deviceManager.GetDevices( + new DeviceQuery { AccessToken = token }).Items.FirstOrDefault(); + + if (device is not null) + { + authInfo.IsAuthenticated = true; + var updateToken = false; + + // TODO: Remove these checks for IsNullOrWhiteSpace + if (string.IsNullOrWhiteSpace(authInfo.Client)) + { + authInfo.Client = device.AppName; + } + + if (string.IsNullOrWhiteSpace(authInfo.DeviceId)) + { + authInfo.DeviceId = device.DeviceId; + } + + // Temporary. TODO - allow clients to specify that the token has been shared with a casting device + var allowTokenInfoUpdate = !authInfo.Client.Contains("chromecast", StringComparison.OrdinalIgnoreCase); + + if (string.IsNullOrWhiteSpace(authInfo.Device)) + { + authInfo.Device = device.DeviceName; + } + else if (!string.Equals(authInfo.Device, device.DeviceName, StringComparison.OrdinalIgnoreCase)) + { + if (allowTokenInfoUpdate) + { + updateToken = true; + device.DeviceName = authInfo.Device; + } + } + + if (string.IsNullOrWhiteSpace(authInfo.Version)) + { + authInfo.Version = device.AppVersion; + } + else if (!string.Equals(authInfo.Version, device.AppVersion, StringComparison.OrdinalIgnoreCase)) + { + if (allowTokenInfoUpdate) + { + updateToken = true; + device.AppVersion = authInfo.Version; + } + } + + if ((DateTime.UtcNow - device.DateLastActivity).TotalMinutes > 3) + { + device.DateLastActivity = DateTime.UtcNow; + updateToken = true; + } + + authInfo.User = _userManager.GetUserById(device.UserId); + + if (updateToken) + { + await _deviceManager.UpdateDevice(device).ConfigureAwait(false); + } + } + else + { + var key = await dbContext.ApiKeys.FirstOrDefaultAsync(apiKey => apiKey.AccessToken == token).ConfigureAwait(false); + if (key is not null) + { + authInfo.IsAuthenticated = true; + authInfo.Client = key.Name; + authInfo.Token = key.AccessToken; + if (string.IsNullOrWhiteSpace(authInfo.DeviceId)) + { + authInfo.DeviceId = _serverApplicationHost.SystemId; + } + + if (string.IsNullOrWhiteSpace(authInfo.Device)) + { + authInfo.Device = _serverApplicationHost.Name; + } + + if (string.IsNullOrWhiteSpace(authInfo.Version)) + { + authInfo.Version = _serverApplicationHost.ApplicationVersionString; + } + + authInfo.IsApiKey = true; + } + } + + return authInfo; + } + } + + /// <summary> + /// Gets the auth. + /// </summary> + /// <param name="httpReq">The HTTP request.</param> + /// <returns>Dictionary{System.StringSystem.String}.</returns> + private Dictionary<string, string>? GetAuthorizationDictionary(HttpRequest httpReq) + { + var auth = httpReq.Headers[HeaderNames.Authorization]; + + if (_configurationManager.Configuration.EnableLegacyAuthorization && string.IsNullOrEmpty(auth)) + { + auth = httpReq.Headers["X-Emby-Authorization"]; + } + + return auth.Count > 0 ? GetAuthorization(auth[0]) : null; + } + + /// <summary> + /// Gets the authorization. + /// </summary> + /// <param name="authorizationHeader">The authorization header.</param> + /// <returns>Dictionary{System.StringSystem.String}.</returns> + private Dictionary<string, string>? GetAuthorization(ReadOnlySpan<char> authorizationHeader) + { + var firstSpace = authorizationHeader.IndexOf(' '); + + // There should be at least two parts + if (firstSpace == -1) + { + return null; + } + + var name = authorizationHeader[..firstSpace]; + + var validName = name.Equals("MediaBrowser", StringComparison.OrdinalIgnoreCase); + validName = validName || (_configurationManager.Configuration.EnableLegacyAuthorization && name.Equals("Emby", StringComparison.OrdinalIgnoreCase)); + + if (!validName) + { + return null; + } + + // Remove up until the first space + authorizationHeader = authorizationHeader[(firstSpace + 1)..]; + return GetParts(authorizationHeader); + } + + /// <summary> + /// Get the authorization header components. + /// </summary> + /// <param name="authorizationHeader">The authorization header.</param> + /// <returns>Dictionary{System.StringSystem.String}.</returns> + public static Dictionary<string, string> GetParts(ReadOnlySpan<char> authorizationHeader) + { + var result = new Dictionary<string, string>(); + var escaped = false; + int start = 0; + string key = string.Empty; + + int i; + for (i = 0; i < authorizationHeader.Length; i++) + { + var token = authorizationHeader[i]; + if (token == '"' || token == ',') + { + // Applying a XOR logic to evaluate whether it is opening or closing a value + escaped = (!escaped) == (token == '"'); + if (token == ',' && !escaped) + { + // Meeting a comma after a closing escape char means the value is complete + if (start < i) + { + result[key] = WebUtility.UrlDecode(authorizationHeader[start..i].Trim('"').ToString()); + key = string.Empty; + } + + start = i + 1; + } + } + else if (!escaped && token == '=') + { + key = authorizationHeader[start.. i].Trim().ToString(); + start = i + 1; + } + } + + // Add last value + if (start < i) + { + result[key] = WebUtility.UrlDecode(authorizationHeader[start..i].Trim('"').ToString()); + } + + return result; + } + } +} diff --git a/Jellyfin.Server.Implementations/StorageHelpers/StorageHelper.cs b/Jellyfin.Server.Implementations/StorageHelpers/StorageHelper.cs new file mode 100644 index 00000000..ce628a04 --- /dev/null +++ b/Jellyfin.Server.Implementations/StorageHelpers/StorageHelper.cs @@ -0,0 +1,108 @@ +using System; +using System.Globalization; +using System.IO; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.System; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.StorageHelpers; + +/// <summary> +/// Contains methods to help with checking for storage and returning storage data for jellyfin folders. +/// </summary> +public static class StorageHelper +{ + private const long TwoGigabyte = 2_147_483_647L; + private static readonly string[] _byteHumanizedSuffixes = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"]; + + /// <summary> + /// Tests the available storage capacity on the jellyfin paths with estimated minimum values. + /// </summary> + /// <param name="applicationPaths">The application paths.</param> + /// <param name="logger">Logger.</param> + public static void TestCommonPathsForStorageCapacity(IApplicationPaths applicationPaths, ILogger logger) + { + TestDataDirectorySize(applicationPaths.DataPath, logger, TwoGigabyte); + TestDataDirectorySize(applicationPaths.CachePath, logger, TwoGigabyte); + TestDataDirectorySize(applicationPaths.ProgramDataPath, logger, TwoGigabyte); + } + + /// <summary> + /// Gets the free space of a specific directory. + /// </summary> + /// <param name="path">Path to a folder.</param> + /// <returns>The number of bytes available space.</returns> + public static FolderStorageInfo GetFreeSpaceOf(string path) + { + try + { + var driveInfo = new DriveInfo(path); + return new FolderStorageInfo() + { + Path = path, + FreeSpace = driveInfo.AvailableFreeSpace, + UsedSpace = driveInfo.TotalSize - driveInfo.AvailableFreeSpace, + StorageType = driveInfo.DriveType.ToString(), + DeviceId = driveInfo.Name, + }; + } + catch + { + return new FolderStorageInfo() + { + Path = path, + FreeSpace = -1, + UsedSpace = -1, + StorageType = null, + DeviceId = null + }; + } + } + + /// <summary> + /// Gets the underlying drive data from a given path and checks if the available storage capacity matches the threshold. + /// </summary> + /// <param name="path">The path to a folder to evaluate.</param> + /// <param name="logger">The logger.</param> + /// <param name="threshold">The threshold to check for or -1 to just log the data.</param> + /// <exception cref="InvalidOperationException">Thrown when the threshold is not available on the underlying storage.</exception> + private static void TestDataDirectorySize(string path, ILogger logger, long threshold = -1) + { + logger.LogDebug("Check path {TestPath} for storage capacity", path); + Directory.CreateDirectory(path); + + var drive = new DriveInfo(path); + if (threshold != -1 && drive.AvailableFreeSpace < threshold) + { + throw new InvalidOperationException($"The path `{path}` has insufficient free space. Available: {HumanizeStorageSize(drive.AvailableFreeSpace)}, Required: {HumanizeStorageSize(threshold)}."); + } + + logger.LogInformation( + "Storage path `{TestPath}` ({StorageType}) successfully checked with {FreeSpace} free which is over the minimum of {MinFree}.", + path, + drive.DriveType, + HumanizeStorageSize(drive.AvailableFreeSpace), + HumanizeStorageSize(threshold)); + } + + /// <summary> + /// Formats a size in bytes into a common human readable form. + /// </summary> + /// <remarks> + /// Taken and slightly modified from https://stackoverflow.com/a/4975942/1786007 . + /// </remarks> + /// <param name="byteCount">The size in bytes.</param> + /// <returns>A human readable approximate representation of the argument.</returns> + public static string HumanizeStorageSize(long byteCount) + { + if (byteCount == 0) + { + return $"0{_byteHumanizedSuffixes[0]}"; + } + + var bytes = Math.Abs(byteCount); + var place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); + var num = Math.Round(bytes / Math.Pow(1024, place), 1); + return (Math.Sign(byteCount) * num).ToString(CultureInfo.InvariantCulture) + _byteHumanizedSuffixes[place]; + } +} diff --git a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs new file mode 100644 index 00000000..4505a377 --- /dev/null +++ b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs @@ -0,0 +1,695 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using AsyncKeyedLock; +using J2N.Collections.Generic.Extensions; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Trickplay; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.Trickplay; + +/// <summary> +/// ITrickplayManager implementation. +/// </summary> +public class TrickplayManager : ITrickplayManager +{ + private readonly ILogger<TrickplayManager> _logger; + private readonly IMediaEncoder _mediaEncoder; + private readonly IFileSystem _fileSystem; + private readonly EncodingHelper _encodingHelper; + private readonly IServerConfigurationManager _config; + private readonly IImageEncoder _imageEncoder; + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + private readonly IApplicationPaths _appPaths; + private readonly IPathManager _pathManager; + + private static readonly AsyncNonKeyedLocker _resourcePool = new(1); + private static readonly string[] _trickplayImgExtensions = [".jpg"]; + + /// <summary> + /// Initializes a new instance of the <see cref="TrickplayManager"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="mediaEncoder">The media encoder.</param> + /// <param name="fileSystem">The file system.</param> + /// <param name="encodingHelper">The encoding helper.</param> + /// <param name="config">The server configuration manager.</param> + /// <param name="imageEncoder">The image encoder.</param> + /// <param name="dbProvider">The database provider.</param> + /// <param name="appPaths">The application paths.</param> + /// <param name="pathManager">The path manager.</param> + public TrickplayManager( + ILogger<TrickplayManager> logger, + IMediaEncoder mediaEncoder, + IFileSystem fileSystem, + EncodingHelper encodingHelper, + IServerConfigurationManager config, + IImageEncoder imageEncoder, + IDbContextFactory<JellyfinDbContext> dbProvider, + IApplicationPaths appPaths, + IPathManager pathManager) + { + _logger = logger; + _mediaEncoder = mediaEncoder; + _fileSystem = fileSystem; + _encodingHelper = encodingHelper; + _config = config; + _imageEncoder = imageEncoder; + _dbProvider = dbProvider; + _appPaths = appPaths; + _pathManager = pathManager; + } + + /// <inheritdoc /> + public async Task MoveGeneratedTrickplayDataAsync(Video video, LibraryOptions libraryOptions, CancellationToken cancellationToken) + { + var options = _config.Configuration.TrickplayOptions; + if (libraryOptions is null || !libraryOptions.EnableTrickplayImageExtraction || !CanGenerateTrickplay(video, options.Interval)) + { + return; + } + + var existingTrickplayResolutions = await GetTrickplayResolutions(video.Id).ConfigureAwait(false); + foreach (var resolution in existingTrickplayResolutions) + { + cancellationToken.ThrowIfCancellationRequested(); + var existingResolution = resolution.Key; + var tileWidth = resolution.Value.TileWidth; + var tileHeight = resolution.Value.TileHeight; + var shouldBeSavedWithMedia = libraryOptions is not null && libraryOptions.SaveTrickplayWithMedia; + var localOutputDir = new DirectoryInfo(GetTrickplayDirectory(video, tileWidth, tileHeight, existingResolution, false)); + var mediaOutputDir = new DirectoryInfo(GetTrickplayDirectory(video, tileWidth, tileHeight, existingResolution, true)); + if (shouldBeSavedWithMedia && localOutputDir.Exists) + { + var localDirFiles = localOutputDir.EnumerateFiles(); + var mediaDirExists = mediaOutputDir.Exists; + if (localDirFiles.Any() && ((mediaDirExists && mediaOutputDir.EnumerateFiles().Any()) || !mediaDirExists)) + { + // Move images from local dir to media dir + MoveContent(localOutputDir.FullName, mediaOutputDir.FullName); + _logger.LogInformation("Moved trickplay images for {ItemName} to {Location}", video.Name, mediaOutputDir); + } + } + else if (!shouldBeSavedWithMedia && mediaOutputDir.Exists) + { + var mediaDirFiles = mediaOutputDir.EnumerateFiles(); + var localDirExists = localOutputDir.Exists; + if (mediaDirFiles.Any() && ((localDirExists && localOutputDir.EnumerateFiles().Any()) || !localDirExists)) + { + // Move images from media dir to local dir + MoveContent(mediaOutputDir.FullName, localOutputDir.FullName); + _logger.LogInformation("Moved trickplay images for {ItemName} to {Location}", video.Name, localOutputDir); + } + } + } + } + + private void MoveContent(string sourceFolder, string destinationFolder) + { + _fileSystem.MoveDirectory(sourceFolder, destinationFolder); + var parent = Directory.GetParent(sourceFolder); + if (parent is not null) + { + var parentContent = parent.EnumerateDirectories(); + if (!parentContent.Any()) + { + parent.Delete(); + } + } + } + + /// <inheritdoc /> + public async Task RefreshTrickplayDataAsync(Video video, bool replace, LibraryOptions libraryOptions, CancellationToken cancellationToken) + { + var options = _config.Configuration.TrickplayOptions; + if (!CanGenerateTrickplay(video, options.Interval) || libraryOptions is null) + { + return; + } + + var dbContext = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + var saveWithMedia = libraryOptions.SaveTrickplayWithMedia; + var trickplayDirectory = _pathManager.GetTrickplayDirectory(video, saveWithMedia); + if (!libraryOptions.EnableTrickplayImageExtraction || replace) + { + // Prune existing data + if (Directory.Exists(trickplayDirectory)) + { + try + { + Directory.Delete(trickplayDirectory, true); + } + catch (Exception ex) + { + _logger.LogWarning("Unable to clear trickplay directory: {Directory}: {Exception}", trickplayDirectory, ex); + } + } + + await dbContext.TrickplayInfos + .Where(i => i.ItemId.Equals(video.Id)) + .ExecuteDeleteAsync(cancellationToken) + .ConfigureAwait(false); + + if (!replace) + { + return; + } + } + + _logger.LogDebug("Trickplay refresh for {ItemId} (replace existing: {Replace})", video.Id, replace); + + if (options.Interval < 1000) + { + _logger.LogWarning("Trickplay image interval {Interval} is too small, reset to the minimum valid value of 1000", options.Interval); + options.Interval = 1000; + } + + foreach (var width in options.WidthResolutions) + { + cancellationToken.ThrowIfCancellationRequested(); + await RefreshTrickplayDataInternal( + video, + replace, + width, + options, + saveWithMedia, + cancellationToken).ConfigureAwait(false); + } + + // Cleanup old trickplay files + if (Directory.Exists(trickplayDirectory)) + { + var existingFolders = Directory.GetDirectories(trickplayDirectory).ToList(); + var trickplayInfos = await dbContext.TrickplayInfos + .AsNoTracking() + .Where(i => i.ItemId.Equals(video.Id)) + .ToListAsync(cancellationToken) + .ConfigureAwait(false); + var expectedFolders = trickplayInfos.Select(i => GetTrickplayDirectory(video, i.TileWidth, i.TileHeight, i.Width, saveWithMedia)).ToList(); + var foldersToRemove = existingFolders.Except(expectedFolders); + foreach (var folder in foldersToRemove) + { + try + { + _logger.LogWarning("Pruning trickplay files for {Item}", video.Path); + Directory.Delete(folder, true); + } + catch (Exception ex) + { + _logger.LogWarning("Unable to remove trickplay directory: {Directory}: {Exception}", folder, ex); + } + } + } + } + } + + private async Task RefreshTrickplayDataInternal( + Video video, + bool replace, + int width, + TrickplayOptions options, + bool saveWithMedia, + CancellationToken cancellationToken) + { + var imgTempDir = string.Empty; + + using (await _resourcePool.LockAsync(cancellationToken).ConfigureAwait(false)) + { + try + { + // Extract images + // Note: Media sources under parent items exist as their own video/item as well. Only use this video stream for trickplay. + var mediaSource = video.GetMediaSources(false).FirstOrDefault(source => Guid.Parse(source.Id).Equals(video.Id)); + + if (mediaSource is null) + { + _logger.LogDebug("Found no matching media source for item {ItemId}", video.Id); + return; + } + + var mediaPath = mediaSource.Path; + if (!File.Exists(mediaPath)) + { + _logger.LogWarning("Media not found at {Path} for item {ItemID}", mediaPath, video.Id); + return; + } + + // We support video backdrops, but we should not generate trickplay images for them + var parentDirectory = Directory.GetParent(video.Path); + if (parentDirectory is not null && string.Equals(parentDirectory.Name, "backdrops", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogDebug("Ignoring backdrop media found at {Path} for item {ItemID}", video.Path, video.Id); + return; + } + + // The width has to be even, otherwise a lot of filters will not be able to sample it + var actualWidth = 2 * (width / 2); + + // Force using the video width when the trickplay setting has a too large width + if (mediaSource.VideoStream.Width is not null && mediaSource.VideoStream.Width < width) + { + _logger.LogWarning("Video width {VideoWidth} is smaller than trickplay setting {TrickPlayWidth}, using video width for thumbnails", mediaSource.VideoStream.Width, width); + actualWidth = 2 * ((int)mediaSource.VideoStream.Width / 2); + } + + var tileWidth = options.TileWidth; + var tileHeight = options.TileHeight; + var outputDir = new DirectoryInfo(GetTrickplayDirectory(video, tileWidth, tileHeight, actualWidth, saveWithMedia)); + + // Import existing trickplay tiles + if (!replace && outputDir.Exists) + { + var existingFiles = outputDir.GetFiles(); + if (existingFiles.Length > 0) + { + var hasTrickplayResolution = await HasTrickplayResolutionAsync(video.Id, actualWidth).ConfigureAwait(false); + if (hasTrickplayResolution) + { + _logger.LogDebug("Found existing trickplay files for {ItemId}.", video.Id); + return; + } + + // Import tiles + var localTrickplayInfo = new TrickplayInfo + { + ItemId = video.Id, + Width = width, + Interval = options.Interval, + TileWidth = options.TileWidth, + TileHeight = options.TileHeight, + ThumbnailCount = existingFiles.Length, + Height = 0, + Bandwidth = 0 + }; + + foreach (var tile in existingFiles) + { + var image = _imageEncoder.GetImageSize(tile.FullName); + localTrickplayInfo.Height = Math.Max(localTrickplayInfo.Height, (int)Math.Ceiling((double)image.Height / localTrickplayInfo.TileHeight)); + var bitrate = (int)Math.Ceiling((decimal)tile.Length * 8 / localTrickplayInfo.TileWidth / localTrickplayInfo.TileHeight / (localTrickplayInfo.Interval / 1000)); + localTrickplayInfo.Bandwidth = Math.Max(localTrickplayInfo.Bandwidth, bitrate); + } + + await SaveTrickplayInfo(localTrickplayInfo).ConfigureAwait(false); + + _logger.LogDebug("Imported existing trickplay files for {ItemId}.", video.Id); + return; + } + } + + // Generate trickplay tiles + var mediaStream = mediaSource.VideoStream; + var container = mediaSource.Container; + + _logger.LogInformation("Creating trickplay files at {Width} width, for {Path} [ID: {ItemId}]", actualWidth, mediaPath, video.Id); + imgTempDir = await _mediaEncoder.ExtractVideoImagesOnIntervalAccelerated( + mediaPath, + container, + mediaSource, + mediaStream, + actualWidth, + TimeSpan.FromMilliseconds(options.Interval), + options.EnableHwAcceleration, + options.EnableHwEncoding, + options.ProcessThreads, + options.Qscale, + options.ProcessPriority, + options.EnableKeyFrameOnlyExtraction, + _encodingHelper, + cancellationToken).ConfigureAwait(false); + + if (string.IsNullOrEmpty(imgTempDir) || !Directory.Exists(imgTempDir)) + { + throw new InvalidOperationException("Null or invalid directory from media encoder."); + } + + var images = _fileSystem.GetFiles(imgTempDir, _trickplayImgExtensions, false, false) + .Select(i => i.FullName) + .OrderBy(i => i) + .ToList(); + + // Create tiles + var trickplayInfo = CreateTiles(images, actualWidth, options, outputDir.FullName); + + // Save tiles info + try + { + if (trickplayInfo is not null) + { + trickplayInfo.ItemId = video.Id; + await SaveTrickplayInfo(trickplayInfo).ConfigureAwait(false); + + _logger.LogInformation("Finished creation of trickplay files for {0}", mediaPath); + } + else + { + throw new InvalidOperationException("Null trickplay tiles info from CreateTiles."); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while saving trickplay tiles info."); + + // Make sure no files stay in metadata folders on failure + // if tiles info wasn't saved. + outputDir.Delete(true); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error creating trickplay images."); + } + finally + { + if (!string.IsNullOrEmpty(imgTempDir)) + { + Directory.Delete(imgTempDir, true); + } + } + } + } + + /// <inheritdoc /> + public TrickplayInfo CreateTiles(IReadOnlyList<string> images, int width, TrickplayOptions options, string outputDir) + { + if (images.Count == 0) + { + throw new ArgumentException("Can't create trickplay from 0 images."); + } + + var workDir = Path.Combine(_appPaths.TempDirectory, "trickplay_" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(workDir); + + var trickplayInfo = new TrickplayInfo + { + Width = width, + Interval = options.Interval, + TileWidth = options.TileWidth, + TileHeight = options.TileHeight, + ThumbnailCount = images.Count, + // Set during image generation + Height = 0, + Bandwidth = 0 + }; + + /* + * Generate trickplay tiles from sets of thumbnails + */ + var imageOptions = new ImageCollageOptions + { + Width = trickplayInfo.TileWidth, + Height = trickplayInfo.TileHeight + }; + + var thumbnailsPerTile = trickplayInfo.TileWidth * trickplayInfo.TileHeight; + var requiredTiles = (int)Math.Ceiling((double)images.Count / thumbnailsPerTile); + + for (int i = 0; i < requiredTiles; i++) + { + // Set output/input paths + var tilePath = Path.Combine(workDir, $"{i}.jpg"); + + imageOptions.OutputPath = tilePath; + imageOptions.InputPaths = images.Skip(i * thumbnailsPerTile).Take(Math.Min(thumbnailsPerTile, images.Count - (i * thumbnailsPerTile))).ToList(); + + // Generate image and use returned height for tiles info + var height = _imageEncoder.CreateTrickplayTile(imageOptions, options.JpegQuality, trickplayInfo.Width, trickplayInfo.Height != 0 ? trickplayInfo.Height : null); + if (trickplayInfo.Height == 0) + { + trickplayInfo.Height = height; + } + + // Update bitrate + var bitrate = (int)Math.Ceiling(new FileInfo(tilePath).Length * 8m / trickplayInfo.TileWidth / trickplayInfo.TileHeight / (trickplayInfo.Interval / 1000m)); + trickplayInfo.Bandwidth = Math.Max(trickplayInfo.Bandwidth, bitrate); + } + + /* + * Move trickplay tiles to output directory + */ + Directory.CreateDirectory(Directory.GetParent(outputDir)!.FullName); + + // Replace existing tiles if they already exist + if (Directory.Exists(outputDir)) + { + Directory.Delete(outputDir, true); + } + + _fileSystem.MoveDirectory(workDir, outputDir); + + return trickplayInfo; + } + + private bool CanGenerateTrickplay(Video video, int interval) + { + var videoType = video.VideoType; + if (videoType == VideoType.Iso || videoType == VideoType.Dvd || videoType == VideoType.BluRay) + { + return false; + } + + if (video.IsPlaceHolder) + { + return false; + } + + if (video.IsShortcut) + { + return false; + } + + if (!video.IsCompleteMedia) + { + return false; + } + + if (!video.RunTimeTicks.HasValue || video.RunTimeTicks.Value < TimeSpan.FromMilliseconds(interval).Ticks) + { + return false; + } + + // Can't extract images if there are no video streams + return video.GetMediaStreams().Count > 0; + } + + /// <inheritdoc /> + public async Task<Dictionary<int, TrickplayInfo>> GetTrickplayResolutions(Guid itemId) + { + var trickplayResolutions = new Dictionary<int, TrickplayInfo>(); + + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + var trickplayInfos = await dbContext.TrickplayInfos + .AsNoTracking() + .Where(i => i.ItemId.Equals(itemId)) + .ToListAsync() + .ConfigureAwait(false); + + foreach (var info in trickplayInfos) + { + trickplayResolutions[info.Width] = info; + } + } + + return trickplayResolutions; + } + + /// <inheritdoc /> + public async Task<IReadOnlyList<TrickplayInfo>> GetTrickplayItemsAsync(int limit, int offset) + { + IReadOnlyList<TrickplayInfo> trickplayItems; + + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + trickplayItems = await dbContext.TrickplayInfos + .AsNoTracking() + .OrderBy(i => i.ItemId) + .Skip(offset) + .Take(limit) + .ToListAsync() + .ConfigureAwait(false); + } + + return trickplayItems; + } + + /// <inheritdoc /> + public async Task SaveTrickplayInfo(TrickplayInfo info) + { + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + var oldInfo = await dbContext.TrickplayInfos.FindAsync(info.ItemId, info.Width).ConfigureAwait(false); + if (oldInfo is not null) + { + dbContext.TrickplayInfos.Remove(oldInfo); + } + + dbContext.Add(info); + + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + } + + /// <inheritdoc /> + public async Task DeleteTrickplayDataAsync(Guid itemId, CancellationToken cancellationToken) + { + var dbContext = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + await dbContext.TrickplayInfos.Where(i => i.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false); + } + + /// <inheritdoc /> + public async Task<Dictionary<string, Dictionary<int, TrickplayInfo>>> GetTrickplayManifest(BaseItem item) + { + var trickplayManifest = new Dictionary<string, Dictionary<int, TrickplayInfo>>(); + foreach (var mediaSource in item.GetMediaSources(false)) + { + if (mediaSource.IsRemote || !Guid.TryParse(mediaSource.Id, out var mediaSourceId)) + { + continue; + } + + var trickplayResolutions = await GetTrickplayResolutions(mediaSourceId).ConfigureAwait(false); + + if (trickplayResolutions.Count > 0) + { + trickplayManifest[mediaSource.Id] = trickplayResolutions; + } + } + + return trickplayManifest; + } + + /// <inheritdoc /> + public async Task<string> GetTrickplayTilePathAsync(BaseItem item, int width, int index, bool saveWithMedia) + { + var trickplayResolutions = await GetTrickplayResolutions(item.Id).ConfigureAwait(false); + if (trickplayResolutions is not null && trickplayResolutions.TryGetValue(width, out var trickplayInfo)) + { + return Path.Combine(GetTrickplayDirectory(item, trickplayInfo.TileWidth, trickplayInfo.TileHeight, width, saveWithMedia), index + ".jpg"); + } + + return string.Empty; + } + + /// <inheritdoc /> + public async Task<string?> GetHlsPlaylist(Guid itemId, int width, string? apiKey) + { + var trickplayResolutions = await GetTrickplayResolutions(itemId).ConfigureAwait(false); + if (trickplayResolutions is not null && trickplayResolutions.TryGetValue(width, out var trickplayInfo)) + { + var builder = new StringBuilder(128); + + if (trickplayInfo.ThumbnailCount > 0) + { + const string urlFormat = "{0}.jpg?MediaSourceId={1}&ApiKey={2}"; + const string decimalFormat = "{0:0.###}"; + + var resolution = $"{trickplayInfo.Width}x{trickplayInfo.Height}"; + var layout = $"{trickplayInfo.TileWidth}x{trickplayInfo.TileHeight}"; + var thumbnailsPerTile = trickplayInfo.TileWidth * trickplayInfo.TileHeight; + var thumbnailDuration = trickplayInfo.Interval / 1000d; + var infDuration = thumbnailDuration * thumbnailsPerTile; + var tileCount = (int)Math.Ceiling((decimal)trickplayInfo.ThumbnailCount / thumbnailsPerTile); + + builder + .AppendLine("#EXTM3U") + .Append("#EXT-X-TARGETDURATION:") + .AppendLine(tileCount.ToString(CultureInfo.InvariantCulture)) + .AppendLine("#EXT-X-VERSION:7") + .AppendLine("#EXT-X-MEDIA-SEQUENCE:1") + .AppendLine("#EXT-X-PLAYLIST-TYPE:VOD") + .AppendLine("#EXT-X-IMAGES-ONLY"); + + for (int i = 0; i < tileCount; i++) + { + // All tiles prior to the last must contain full amount of thumbnails (no black). + if (i == tileCount - 1) + { + thumbnailsPerTile = trickplayInfo.ThumbnailCount - (i * thumbnailsPerTile); + infDuration = thumbnailDuration * thumbnailsPerTile; + } + + // EXTINF + builder + .Append("#EXTINF:") + .AppendFormat(CultureInfo.InvariantCulture, decimalFormat, infDuration) + .AppendLine(","); + + // EXT-X-TILES + builder + .Append("#EXT-X-TILES:RESOLUTION=") + .Append(resolution) + .Append(",LAYOUT=") + .Append(layout) + .Append(",DURATION=") + .AppendFormat(CultureInfo.InvariantCulture, decimalFormat, thumbnailDuration) + .AppendLine(); + + // URL + builder + .AppendFormat( + CultureInfo.InvariantCulture, + urlFormat, + i.ToString(CultureInfo.InvariantCulture), + itemId.ToString("N"), + apiKey) + .AppendLine(); + } + + builder.AppendLine("#EXT-X-ENDLIST"); + return builder.ToString(); + } + } + + return null; + } + + /// <inheritdoc /> + public string GetTrickplayDirectory(BaseItem item, int tileWidth, int tileHeight, int width, bool saveWithMedia = false) + { + var path = _pathManager.GetTrickplayDirectory(item, saveWithMedia); + var subdirectory = string.Format( + CultureInfo.InvariantCulture, + "{0} - {1}x{2}", + width.ToString(CultureInfo.InvariantCulture), + tileWidth.ToString(CultureInfo.InvariantCulture), + tileHeight.ToString(CultureInfo.InvariantCulture)); + + return Path.Combine(path, subdirectory); + } + + private async Task<bool> HasTrickplayResolutionAsync(Guid itemId, int width) + { + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + return await dbContext.TrickplayInfos + .AsNoTracking() + .Where(i => i.ItemId.Equals(itemId)) + .AnyAsync(i => i.Width == width) + .ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs new file mode 100644 index 00000000..446849b6 --- /dev/null +++ b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs @@ -0,0 +1,111 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Model.Cryptography; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.Users +{ + /// <summary> + /// The default authentication provider. + /// </summary> + public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser + { + private readonly ILogger<DefaultAuthenticationProvider> _logger; + private readonly ICryptoProvider _cryptographyProvider; + + /// <summary> + /// Initializes a new instance of the <see cref="DefaultAuthenticationProvider"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="cryptographyProvider">The cryptography provider.</param> + public DefaultAuthenticationProvider(ILogger<DefaultAuthenticationProvider> logger, ICryptoProvider cryptographyProvider) + { + _logger = logger; + _cryptographyProvider = cryptographyProvider; + } + + /// <inheritdoc /> + public string Name => "Default"; + + /// <inheritdoc /> + public bool IsEnabled => true; + + /// <inheritdoc /> + // This is dumb and an artifact of the backwards way auth providers were designed. + // This version of authenticate was never meant to be called, but needs to be here for interface compat + // Only the providers that don't provide local user support use this + public Task<ProviderAuthenticationResult> Authenticate(string username, string password) + { + throw new NotImplementedException(); + } + + /// <inheritdoc /> + // This is the version that we need to use for local users. Because reasons. + public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User? resolvedUser) + { + [DoesNotReturn] + static void ThrowAuthenticationException() + { + throw new AuthenticationException("Invalid username or password"); + } + + if (resolvedUser is null) + { + ThrowAuthenticationException(); + } + + // As long as jellyfin supports password-less users, we need this little block here to accommodate + if (string.IsNullOrEmpty(resolvedUser.Password) && string.IsNullOrEmpty(password)) + { + return Task.FromResult(new ProviderAuthenticationResult + { + Username = username + }); + } + + // Handle the case when the stored password is null, but the user tried to login with a password + if (resolvedUser.Password is null) + { + ThrowAuthenticationException(); + } + + PasswordHash readyHash = PasswordHash.Parse(resolvedUser.Password); + if (!_cryptographyProvider.Verify(readyHash, password)) + { + ThrowAuthenticationException(); + } + + // Migrate old hashes to the new default + if (!string.Equals(readyHash.Id, _cryptographyProvider.DefaultHashMethod, StringComparison.Ordinal) + || int.Parse(readyHash.Parameters["iterations"], CultureInfo.InvariantCulture) != Constants.DefaultIterations) + { + _logger.LogInformation("Migrating password hash of {User} to the latest default", username); + ChangePassword(resolvedUser, password); + } + + return Task.FromResult(new ProviderAuthenticationResult + { + Username = username + }); + } + + /// <inheritdoc /> + public Task ChangePassword(User user, string newPassword) + { + if (string.IsNullOrEmpty(newPassword)) + { + user.Password = null; + return Task.CompletedTask; + } + + PasswordHash newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword); + user.Password = newPasswordHash.ToString(); + + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs new file mode 100644 index 00000000..49a9fda9 --- /dev/null +++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Security.Cryptography; +using System.Text.Json; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Common; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Users; + +namespace Jellyfin.Server.Implementations.Users +{ + /// <summary> + /// The default password reset provider. + /// </summary> + public class DefaultPasswordResetProvider : IPasswordResetProvider + { + private const string BaseResetFileName = "passwordreset"; + + private readonly IApplicationHost _appHost; + + private readonly string _passwordResetFileBase; + private readonly string _passwordResetFileBaseDir; + + /// <summary> + /// Initializes a new instance of the <see cref="DefaultPasswordResetProvider"/> class. + /// </summary> + /// <param name="configurationManager">The configuration manager.</param> + /// <param name="appHost">The application host.</param> + public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IApplicationHost appHost) + { + _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; + _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName); + _appHost = appHost; + // TODO: Remove the circular dependency on UserManager + } + + /// <inheritdoc /> + public string Name => "Default Password Reset Provider"; + + /// <inheritdoc /> + public bool IsEnabled => true; + + /// <inheritdoc /> + public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin) + { + var userManager = _appHost.Resolve<IUserManager>(); + var usersReset = new List<string>(); + foreach (var resetFile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*")) + { + SerializablePasswordReset spr; + var str = AsyncFile.OpenRead(resetFile); + await using (str.ConfigureAwait(false)) + { + spr = await JsonSerializer.DeserializeAsync<SerializablePasswordReset>(str).ConfigureAwait(false) + ?? throw new ResourceNotFoundException($"Provided path ({resetFile}) is not valid."); + } + + if (spr.ExpirationDate < DateTime.UtcNow) + { + File.Delete(resetFile); + } + else if (string.Equals( + spr.Pin.Replace("-", string.Empty, StringComparison.Ordinal), + pin.Replace("-", string.Empty, StringComparison.Ordinal), + StringComparison.Ordinal)) + { + var resetUser = userManager.GetUserByName(spr.UserName) + ?? throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found"); + + await userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); + usersReset.Add(resetUser.Username); + File.Delete(resetFile); + } + } + + if (usersReset.Count < 1) + { + throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}"); + } + + return new PinRedeemResult + { + Success = true, + UsersReset = usersReset.ToArray() + }; + } + + /// <inheritdoc /> + public async Task<ForgotPasswordResult> StartForgotPasswordProcess(User? user, string enteredUsername, bool isInNetwork) + { + DateTime expireTime = DateTime.UtcNow.AddMinutes(30); + var usernameHash = enteredUsername.ToUpperInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture); + var pinFile = _passwordResetFileBase + usernameHash + ".json"; + + if (user is not null && isInNetwork) + { + byte[] bytes = new byte[4]; + RandomNumberGenerator.Fill(bytes); + string pin = BitConverter.ToString(bytes); + + SerializablePasswordReset spr = new SerializablePasswordReset + { + ExpirationDate = expireTime, + Pin = pin, + PinFile = pinFile, + UserName = user.Username + }; + + FileStream fileStream = AsyncFile.Create(pinFile); + await using (fileStream.ConfigureAwait(false)) + { + await JsonSerializer.SerializeAsync(fileStream, spr).ConfigureAwait(false); + } + } + + return new ForgotPasswordResult + { + Action = ForgotPasswordAction.PinCode, + PinExpirationDate = expireTime, + PinFile = pinFile + }; + } + +#nullable disable + private class SerializablePasswordReset : PasswordPinCreationResult + { + public string Pin { get; set; } + + public string UserName { get; set; } + } + } +} diff --git a/Jellyfin.Server.Implementations/Users/DeviceAccessHost.cs b/Jellyfin.Server.Implementations/Users/DeviceAccessHost.cs new file mode 100644 index 00000000..92e2bb4f --- /dev/null +++ b/Jellyfin.Server.Implementations/Users/DeviceAccessHost.cs @@ -0,0 +1,77 @@ +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data; +using Jellyfin.Data.Events; +using Jellyfin.Data.Queries; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using Microsoft.Extensions.Hosting; + +namespace Jellyfin.Server.Implementations.Users; + +/// <summary> +/// <see cref="IHostedService"/> responsible for managing user device permissions. +/// </summary> +public sealed class DeviceAccessHost : IHostedService +{ + private readonly IUserManager _userManager; + private readonly IDeviceManager _deviceManager; + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="DeviceAccessHost"/> class. + /// </summary> + /// <param name="userManager">The <see cref="IUserManager"/>.</param> + /// <param name="deviceManager">The <see cref="IDeviceManager"/>.</param> + /// <param name="sessionManager">The <see cref="ISessionManager"/>.</param> + public DeviceAccessHost(IUserManager userManager, IDeviceManager deviceManager, ISessionManager sessionManager) + { + _userManager = userManager; + _deviceManager = deviceManager; + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public Task StartAsync(CancellationToken cancellationToken) + { + _userManager.OnUserUpdated += OnUserUpdated; + + return Task.CompletedTask; + } + + /// <inheritdoc /> + public Task StopAsync(CancellationToken cancellationToken) + { + _userManager.OnUserUpdated -= OnUserUpdated; + + return Task.CompletedTask; + } + + private async void OnUserUpdated(object? sender, GenericEventArgs<User> e) + { + var user = e.Argument; + if (!user.HasPermission(PermissionKind.EnableAllDevices)) + { + await UpdateDeviceAccess(user).ConfigureAwait(false); + } + } + + private async Task UpdateDeviceAccess(User user) + { + var existing = _deviceManager.GetDevices(new DeviceQuery + { + UserId = user.Id + }).Items; + + foreach (var device in existing) + { + if (!string.IsNullOrEmpty(device.DeviceId) && !_deviceManager.CanAccessDevice(user, device.DeviceId)) + { + await _sessionManager.Logout(device).ConfigureAwait(false); + } + } + } +} diff --git a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs new file mode 100644 index 00000000..0e126fe9 --- /dev/null +++ b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller; +using Microsoft.EntityFrameworkCore; + +namespace Jellyfin.Server.Implementations.Users; + +/// <summary> +/// Manages the storage and retrieval of display preferences through Entity Framework. +/// </summary> +public sealed class DisplayPreferencesManager : IDisplayPreferencesManager +{ + private readonly IDbContextFactory<JellyfinDbContext> _dbContextFactory; + + /// <summary> + /// Initializes a new instance of the <see cref="DisplayPreferencesManager"/> class. + /// </summary> + /// <param name="dbContextFactory">The database context factory.</param> + public DisplayPreferencesManager(IDbContextFactory<JellyfinDbContext> dbContextFactory) + { + _dbContextFactory = dbContextFactory; + } + + /// <inheritdoc /> + public DisplayPreferences GetDisplayPreferences(Guid userId, Guid itemId, string client) + { + using var dbContext = _dbContextFactory.CreateDbContext(); + var prefs = dbContext.DisplayPreferences + .Include(pref => pref.HomeSections) + .FirstOrDefault(pref => + pref.UserId.Equals(userId) && pref.Client == client && pref.ItemId.Equals(itemId)); + + if (prefs is null) + { + prefs = new DisplayPreferences(userId, itemId, client); + dbContext.DisplayPreferences.Add(prefs); + dbContext.SaveChanges(); + } + + return prefs; + } + + /// <inheritdoc /> + public ItemDisplayPreferences GetItemDisplayPreferences(Guid userId, Guid itemId, string client) + { + using var dbContext = _dbContextFactory.CreateDbContext(); + var prefs = dbContext.ItemDisplayPreferences + .FirstOrDefault(pref => pref.UserId.Equals(userId) && pref.ItemId.Equals(itemId) && pref.Client == client); + + if (prefs is null) + { + prefs = new ItemDisplayPreferences(userId, Guid.Empty, client); + dbContext.ItemDisplayPreferences.Add(prefs); + dbContext.SaveChanges(); + } + + return prefs; + } + + /// <inheritdoc /> + public IList<ItemDisplayPreferences> ListItemDisplayPreferences(Guid userId, string client) + { + using var dbContext = _dbContextFactory.CreateDbContext(); + return dbContext.ItemDisplayPreferences + .Where(prefs => prefs.UserId.Equals(userId) && !prefs.ItemId.Equals(default) && prefs.Client == client) + .ToList(); + } + + /// <inheritdoc /> + public Dictionary<string, string?> ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client) + { + using var dbContext = _dbContextFactory.CreateDbContext(); + return dbContext.CustomItemDisplayPreferences + .Where(prefs => prefs.UserId.Equals(userId) + && prefs.ItemId.Equals(itemId) + && prefs.Client == client) + .ToDictionary(prefs => prefs.Key, prefs => prefs.Value); + } + + /// <inheritdoc /> + public void SetCustomItemDisplayPreferences(Guid userId, Guid itemId, string client, Dictionary<string, string?> customPreferences) + { + using var dbContext = _dbContextFactory.CreateDbContext(); + dbContext.CustomItemDisplayPreferences.Where(prefs => prefs.UserId.Equals(userId) + && prefs.ItemId.Equals(itemId) + && prefs.Client == client) + .ExecuteDelete(); + + foreach (var (key, value) in customPreferences) + { + dbContext.CustomItemDisplayPreferences + .Add(new CustomItemDisplayPreferences(userId, itemId, client, key, value)); + } + + dbContext.SaveChanges(); + } + + /// <inheritdoc/> + public void UpdateDisplayPreferences(DisplayPreferences displayPreferences) + { + using var dbContext = _dbContextFactory.CreateDbContext(); + dbContext.DisplayPreferences.Attach(displayPreferences).State = EntityState.Modified; + dbContext.SaveChanges(); + } + + /// <inheritdoc/> + public void UpdateItemDisplayPreferences(ItemDisplayPreferences itemDisplayPreferences) + { + using var dbContext = _dbContextFactory.CreateDbContext(); + dbContext.ItemDisplayPreferences.Attach(itemDisplayPreferences).State = EntityState.Modified; + dbContext.SaveChanges(); + } +} diff --git a/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs new file mode 100644 index 00000000..56b8a7fc --- /dev/null +++ b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Authentication; + +namespace Jellyfin.Server.Implementations.Users +{ + /// <summary> + /// An invalid authentication provider. + /// </summary> + public class InvalidAuthProvider : IAuthenticationProvider + { + /// <inheritdoc /> + public string Name => "InvalidOrMissingAuthenticationProvider"; + + /// <inheritdoc /> + public bool IsEnabled => false; + + /// <inheritdoc /> + public Task<ProviderAuthenticationResult> Authenticate(string username, string password) + { + throw new AuthenticationException("User Account cannot login with this provider. The Normal provider for this user cannot be found"); + } + + /// <inheritdoc /> + public Task ChangePassword(User user, string newPassword) + { + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs new file mode 100644 index 00000000..501cb4fb --- /dev/null +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -0,0 +1,894 @@ +#pragma warning disable CA1307 + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Jellyfin.Data; +using Jellyfin.Data.Enums; +using Jellyfin.Data.Events; +using Jellyfin.Data.Events.Users; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Common; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Users; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.Users +{ + /// <summary> + /// Manages the creation and retrieval of <see cref="User"/> instances. + /// </summary> + public partial class UserManager : IUserManager + { + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + private readonly IEventManager _eventManager; + private readonly INetworkManager _networkManager; + private readonly IApplicationHost _appHost; + private readonly IImageProcessor _imageProcessor; + private readonly ILogger<UserManager> _logger; + private readonly IReadOnlyCollection<IPasswordResetProvider> _passwordResetProviders; + private readonly IReadOnlyCollection<IAuthenticationProvider> _authenticationProviders; + private readonly InvalidAuthProvider _invalidAuthProvider; + private readonly DefaultAuthenticationProvider _defaultAuthenticationProvider; + private readonly DefaultPasswordResetProvider _defaultPasswordResetProvider; + private readonly IServerConfigurationManager _serverConfigurationManager; + + private readonly IDictionary<Guid, User> _users; + + /// <summary> + /// Initializes a new instance of the <see cref="UserManager"/> class. + /// </summary> + /// <param name="dbProvider">The database provider.</param> + /// <param name="eventManager">The event manager.</param> + /// <param name="networkManager">The network manager.</param> + /// <param name="appHost">The application host.</param> + /// <param name="imageProcessor">The image processor.</param> + /// <param name="logger">The logger.</param> + /// <param name="serverConfigurationManager">The system config manager.</param> + /// <param name="passwordResetProviders">The password reset providers.</param> + /// <param name="authenticationProviders">The authentication providers.</param> + public UserManager( + IDbContextFactory<JellyfinDbContext> dbProvider, + IEventManager eventManager, + INetworkManager networkManager, + IApplicationHost appHost, + IImageProcessor imageProcessor, + ILogger<UserManager> logger, + IServerConfigurationManager serverConfigurationManager, + IEnumerable<IPasswordResetProvider> passwordResetProviders, + IEnumerable<IAuthenticationProvider> authenticationProviders) + { + _dbProvider = dbProvider; + _eventManager = eventManager; + _networkManager = networkManager; + _appHost = appHost; + _imageProcessor = imageProcessor; + _logger = logger; + _serverConfigurationManager = serverConfigurationManager; + + _passwordResetProviders = passwordResetProviders.ToList(); + _authenticationProviders = authenticationProviders.ToList(); + + _invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First(); + _defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First(); + _defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First(); + + _users = new ConcurrentDictionary<Guid, User>(); + using var dbContext = _dbProvider.CreateDbContext(); + foreach (var user in dbContext.Users + .AsSplitQuery() + .Include(user => user.Permissions) + .Include(user => user.Preferences) + .Include(user => user.AccessSchedules) + .Include(user => user.ProfileImage) + .AsEnumerable()) + { + _users.Add(user.Id, user); + } + } + + /// <inheritdoc/> + public event EventHandler<GenericEventArgs<User>>? OnUserUpdated; + + /// <inheritdoc/> + public IEnumerable<User> Users => _users.Values; + + /// <inheritdoc/> + public IEnumerable<Guid> UsersIds => _users.Keys; + + // This is some regex that matches only on unicode "word" characters, as well as -, _ and @ + // In theory this will cut out most if not all 'control' characters which should help minimize any weirdness + // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), periods (.) and spaces ( ) + [GeneratedRegex(@"^(?!\s)[\w\ \-'._@+]+(?<!\s)$")] + private static partial Regex ValidUsernameRegex(); + + /// <inheritdoc/> + public User? GetUserById(Guid id) + { + if (id.IsEmpty()) + { + throw new ArgumentException("Guid can't be empty", nameof(id)); + } + + _users.TryGetValue(id, out var user); + return user; + } + + /// <inheritdoc/> + public User? GetUserByName(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Invalid username", nameof(name)); + } + + return _users.Values.FirstOrDefault(u => string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase)); + } + + /// <inheritdoc/> + public async Task RenameUser(User user, string newName) + { + ArgumentNullException.ThrowIfNull(user); + + ThrowIfInvalidUsername(newName); + + if (user.Username.Equals(newName, StringComparison.Ordinal)) + { + throw new ArgumentException("The new and old names must be different."); + } + + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { +#pragma warning disable CA1862 // Use the 'StringComparison' method overloads to perform case-insensitive string comparisons +#pragma warning disable CA1311 // Specify a culture or use an invariant version to avoid implicit dependency on current culture +#pragma warning disable CA1304 // The behavior of 'string.ToUpper()' could vary based on the current user's locale settings + if (await dbContext.Users + .AnyAsync(u => u.Username.ToUpper() == newName.ToUpper() && !u.Id.Equals(user.Id)) + .ConfigureAwait(false)) + { + throw new ArgumentException(string.Format( + CultureInfo.InvariantCulture, + "A user with the name '{0}' already exists.", + newName)); + } +#pragma warning restore CA1304 // The behavior of 'string.ToUpper()' could vary based on the current user's locale settings +#pragma warning restore CA1311 // Specify a culture or use an invariant version to avoid implicit dependency on current culture +#pragma warning restore CA1862 // Use the 'StringComparison' method overloads to perform case-insensitive string comparisons + + user.Username = newName; + await UpdateUserInternalAsync(dbContext, user).ConfigureAwait(false); + } + + var eventArgs = new UserUpdatedEventArgs(user); + await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false); + OnUserUpdated?.Invoke(this, eventArgs); + } + + /// <inheritdoc/> + public async Task UpdateUserAsync(User user) + { + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + await UpdateUserInternalAsync(dbContext, user).ConfigureAwait(false); + } + } + + internal async Task<User> CreateUserInternalAsync(string name, JellyfinDbContext dbContext) + { + // TODO: Remove after user item data is migrated. + var max = await dbContext.Users.AsQueryable().AnyAsync().ConfigureAwait(false) + ? await dbContext.Users.AsQueryable().Select(u => u.InternalId).MaxAsync().ConfigureAwait(false) + : 0; + + var user = new User( + name, + _defaultAuthenticationProvider.GetType().FullName!, + _defaultPasswordResetProvider.GetType().FullName!) + { + InternalId = max + 1 + }; + + user.AddDefaultPermissions(); + user.AddDefaultPreferences(); + + return user; + } + + /// <inheritdoc/> + public async Task<User> CreateUserAsync(string name) + { + ThrowIfInvalidUsername(name); + + if (Users.Any(u => u.Username.Equals(name, StringComparison.OrdinalIgnoreCase))) + { + throw new ArgumentException(string.Format( + CultureInfo.InvariantCulture, + "A user with the name '{0}' already exists.", + name)); + } + + User newUser; + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + newUser = await CreateUserInternalAsync(name, dbContext).ConfigureAwait(false); + + dbContext.Users.Add(newUser); + await dbContext.SaveChangesAsync().ConfigureAwait(false); + _users.Add(newUser.Id, newUser); + } + + await _eventManager.PublishAsync(new UserCreatedEventArgs(newUser)).ConfigureAwait(false); + + return newUser; + } + + /// <inheritdoc/> + public async Task DeleteUserAsync(Guid userId) + { + if (!_users.TryGetValue(userId, out var user)) + { + throw new ResourceNotFoundException(nameof(userId)); + } + + if (_users.Count == 1) + { + throw new InvalidOperationException(string.Format( + CultureInfo.InvariantCulture, + "The user '{0}' cannot be deleted because there must be at least one user in the system.", + user.Username)); + } + + if (user.HasPermission(PermissionKind.IsAdministrator) + && Users.Count(i => i.HasPermission(PermissionKind.IsAdministrator)) == 1) + { + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "The user '{0}' cannot be deleted because there must be at least one admin user in the system.", + user.Username), + nameof(userId)); + } + + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + dbContext.Users.Attach(user); + dbContext.Users.Remove(user); + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + + _users.Remove(userId); + + await _eventManager.PublishAsync(new UserDeletedEventArgs(user)).ConfigureAwait(false); + } + + /// <inheritdoc/> + public Task ResetPassword(User user) + { + return ChangePassword(user, string.Empty); + } + + /// <inheritdoc/> + public async Task ChangePassword(User user, string newPassword) + { + ArgumentNullException.ThrowIfNull(user); + if (user.HasPermission(PermissionKind.IsAdministrator) && string.IsNullOrWhiteSpace(newPassword)) + { + throw new ArgumentException("Admin user passwords must not be empty", nameof(newPassword)); + } + + await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); + await UpdateUserAsync(user).ConfigureAwait(false); + + await _eventManager.PublishAsync(new UserPasswordChangedEventArgs(user)).ConfigureAwait(false); + } + + /// <inheritdoc/> + public UserDto GetUserDto(User user, string? remoteEndPoint = null) + { + var castReceiverApplications = _serverConfigurationManager.Configuration.CastReceiverApplications; + return new UserDto + { + Name = user.Username, + Id = user.Id, + ServerId = _appHost.SystemId, + EnableAutoLogin = user.EnableAutoLogin, + LastLoginDate = user.LastLoginDate, + LastActivityDate = user.LastActivityDate, + PrimaryImageTag = user.ProfileImage is not null ? _imageProcessor.GetImageCacheTag(user) : null, + Configuration = new UserConfiguration + { + SubtitleMode = user.SubtitleMode, + HidePlayedInLatest = user.HidePlayedInLatest, + EnableLocalPassword = user.EnableLocalPassword, + PlayDefaultAudioTrack = user.PlayDefaultAudioTrack, + DisplayCollectionsView = user.DisplayCollectionsView, + DisplayMissingEpisodes = user.DisplayMissingEpisodes, + AudioLanguagePreference = user.AudioLanguagePreference, + RememberAudioSelections = user.RememberAudioSelections, + EnableNextEpisodeAutoPlay = user.EnableNextEpisodeAutoPlay, + RememberSubtitleSelections = user.RememberSubtitleSelections, + SubtitleLanguagePreference = user.SubtitleLanguagePreference ?? string.Empty, + OrderedViews = user.GetPreferenceValues<Guid>(PreferenceKind.OrderedViews), + GroupedFolders = user.GetPreferenceValues<Guid>(PreferenceKind.GroupedFolders), + MyMediaExcludes = user.GetPreferenceValues<Guid>(PreferenceKind.MyMediaExcludes), + LatestItemsExcludes = user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes), + CastReceiverId = string.IsNullOrEmpty(user.CastReceiverId) + ? castReceiverApplications.FirstOrDefault()?.Id + : castReceiverApplications.FirstOrDefault(c => string.Equals(c.Id, user.CastReceiverId, StringComparison.Ordinal))?.Id + ?? castReceiverApplications.FirstOrDefault()?.Id + }, + Policy = new UserPolicy + { + MaxParentalRating = user.MaxParentalRatingScore, + MaxParentalSubRating = user.MaxParentalRatingSubScore, + EnableUserPreferenceAccess = user.EnableUserPreferenceAccess, + RemoteClientBitrateLimit = user.RemoteClientBitrateLimit ?? 0, + AuthenticationProviderId = user.AuthenticationProviderId, + PasswordResetProviderId = user.PasswordResetProviderId, + InvalidLoginAttemptCount = user.InvalidLoginAttemptCount, + LoginAttemptsBeforeLockout = user.LoginAttemptsBeforeLockout ?? -1, + MaxActiveSessions = user.MaxActiveSessions, + IsAdministrator = user.HasPermission(PermissionKind.IsAdministrator), + IsHidden = user.HasPermission(PermissionKind.IsHidden), + IsDisabled = user.HasPermission(PermissionKind.IsDisabled), + EnableSharedDeviceControl = user.HasPermission(PermissionKind.EnableSharedDeviceControl), + EnableRemoteAccess = user.HasPermission(PermissionKind.EnableRemoteAccess), + EnableLiveTvManagement = user.HasPermission(PermissionKind.EnableLiveTvManagement), + EnableLiveTvAccess = user.HasPermission(PermissionKind.EnableLiveTvAccess), + EnableMediaPlayback = user.HasPermission(PermissionKind.EnableMediaPlayback), + EnableAudioPlaybackTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding), + EnableVideoPlaybackTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding), + EnableContentDeletion = user.HasPermission(PermissionKind.EnableContentDeletion), + EnableContentDownloading = user.HasPermission(PermissionKind.EnableContentDownloading), + EnableSyncTranscoding = user.HasPermission(PermissionKind.EnableSyncTranscoding), + EnableMediaConversion = user.HasPermission(PermissionKind.EnableMediaConversion), + EnableAllChannels = user.HasPermission(PermissionKind.EnableAllChannels), + EnableAllDevices = user.HasPermission(PermissionKind.EnableAllDevices), + EnableAllFolders = user.HasPermission(PermissionKind.EnableAllFolders), + EnableRemoteControlOfOtherUsers = user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers), + EnablePlaybackRemuxing = user.HasPermission(PermissionKind.EnablePlaybackRemuxing), + ForceRemoteSourceTranscoding = user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding), + EnablePublicSharing = user.HasPermission(PermissionKind.EnablePublicSharing), + EnableCollectionManagement = user.HasPermission(PermissionKind.EnableCollectionManagement), + EnableSubtitleManagement = user.HasPermission(PermissionKind.EnableSubtitleManagement), + AccessSchedules = user.AccessSchedules.ToArray(), + BlockedTags = user.GetPreference(PreferenceKind.BlockedTags), + AllowedTags = user.GetPreference(PreferenceKind.AllowedTags), + EnabledChannels = user.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels), + EnabledDevices = user.GetPreference(PreferenceKind.EnabledDevices), + EnabledFolders = user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders), + EnableContentDeletionFromFolders = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders), + SyncPlayAccess = user.SyncPlayAccess, + BlockedChannels = user.GetPreferenceValues<Guid>(PreferenceKind.BlockedChannels), + BlockedMediaFolders = user.GetPreferenceValues<Guid>(PreferenceKind.BlockedMediaFolders), + BlockUnratedItems = user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems) + } + }; + } + + /// <inheritdoc/> + public async Task<User?> AuthenticateUser( + string username, + string password, + string remoteEndPoint, + bool isUserSession) + { + if (string.IsNullOrWhiteSpace(username)) + { + _logger.LogInformation("Authentication request without username has been denied (IP: {IP}).", remoteEndPoint); + throw new ArgumentNullException(nameof(username)); + } + + var user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); + var authResult = await AuthenticateLocalUser(username, password, user) + .ConfigureAwait(false); + var authenticationProvider = authResult.AuthenticationProvider; + var success = authResult.Success; + + if (user is null) + { + string updatedUsername = authResult.Username; + + if (success + && authenticationProvider is not null + && authenticationProvider is not DefaultAuthenticationProvider) + { + // Trust the username returned by the authentication provider + username = updatedUsername; + + // Search the database for the user again + // the authentication provider might have created it + user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); + + if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy && user is not null) + { + await UpdatePolicyAsync(user.Id, hasNewUserPolicy.GetNewUserPolicy()).ConfigureAwait(false); + } + } + } + + if (success && user is not null && authenticationProvider is not null) + { + var providerId = authenticationProvider.GetType().FullName; + + if (providerId is not null && !string.Equals(providerId, user.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) + { + user.AuthenticationProviderId = providerId; + await UpdateUserAsync(user).ConfigureAwait(false); + } + } + + if (user is null) + { + _logger.LogInformation( + "Authentication request for {UserName} has been denied (IP: {IP}).", + username, + remoteEndPoint); + throw new AuthenticationException("Invalid username or password entered."); + } + + if (user.HasPermission(PermissionKind.IsDisabled)) + { + _logger.LogInformation( + "Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", + username, + remoteEndPoint); + throw new SecurityException( + $"The {user.Username} account is currently disabled. Please consult with your administrator."); + } + + if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && + !_networkManager.IsInLocalNetwork(remoteEndPoint)) + { + _logger.LogInformation( + "Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", + username, + remoteEndPoint); + throw new SecurityException("Forbidden."); + } + + if (!user.IsParentalScheduleAllowed()) + { + _logger.LogInformation( + "Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", + username, + remoteEndPoint); + throw new SecurityException("User is not allowed access at this time."); + } + + // Update LastActivityDate and LastLoginDate, then save + if (success) + { + if (isUserSession) + { + user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; + } + + user.InvalidLoginAttemptCount = 0; + await UpdateUserAsync(user).ConfigureAwait(false); + _logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Username); + } + else + { + await IncrementInvalidLoginAttemptCount(user).ConfigureAwait(false); + _logger.LogInformation( + "Authentication request for {UserName} has been denied (IP: {IP}).", + user.Username, + remoteEndPoint); + } + + return success ? user : null; + } + + /// <inheritdoc/> + public async Task<ForgotPasswordResult> StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) + { + var user = string.IsNullOrWhiteSpace(enteredUsername) ? null : GetUserByName(enteredUsername); + var passwordResetProvider = GetPasswordResetProvider(user); + + var result = await passwordResetProvider + .StartForgotPasswordProcess(user, enteredUsername, isInNetwork) + .ConfigureAwait(false); + + if (user is not null && isInNetwork) + { + await UpdateUserAsync(user).ConfigureAwait(false); + } + + return result; + } + + /// <inheritdoc/> + public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin) + { + foreach (var provider in _passwordResetProviders) + { + var result = await provider.RedeemPasswordResetPin(pin).ConfigureAwait(false); + + if (result.Success) + { + return result; + } + } + + return new PinRedeemResult(); + } + + /// <inheritdoc /> + public async Task InitializeAsync() + { + // TODO: Refactor the startup wizard so that it doesn't require a user to already exist. + if (_users.Any()) + { + return; + } + + var defaultName = Environment.UserName; + if (string.IsNullOrWhiteSpace(defaultName) || !ValidUsernameRegex().IsMatch(defaultName)) + { + defaultName = "MyJellyfinUser"; + } + + _logger.LogWarning("No users, creating one with username {UserName}", defaultName); + + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false); + newUser.SetPermission(PermissionKind.IsAdministrator, true); + newUser.SetPermission(PermissionKind.EnableContentDeletion, true); + newUser.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, true); + + dbContext.Users.Add(newUser); + await dbContext.SaveChangesAsync().ConfigureAwait(false); + _users.Add(newUser.Id, newUser); + } + } + + /// <inheritdoc/> + public NameIdPair[] GetAuthenticationProviders() + { + return _authenticationProviders + .Where(provider => provider.IsEnabled) + .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1) + .ThenBy(i => i.Name) + .Select(i => new NameIdPair + { + Name = i.Name, + Id = i.GetType().FullName + }) + .ToArray(); + } + + /// <inheritdoc/> + public NameIdPair[] GetPasswordResetProviders() + { + return _passwordResetProviders + .Where(provider => provider.IsEnabled) + .OrderBy(i => i is DefaultPasswordResetProvider ? 0 : 1) + .ThenBy(i => i.Name) + .Select(i => new NameIdPair + { + Name = i.Name, + Id = i.GetType().FullName + }) + .ToArray(); + } + + /// <inheritdoc/> + public async Task UpdateConfigurationAsync(Guid userId, UserConfiguration config) + { + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + var user = dbContext.Users + .Include(u => u.Permissions) + .Include(u => u.Preferences) + .Include(u => u.AccessSchedules) + .Include(u => u.ProfileImage) + .FirstOrDefault(u => u.Id.Equals(userId)) + ?? throw new ArgumentException("No user exists with given Id!"); + + user.SubtitleMode = config.SubtitleMode; + user.HidePlayedInLatest = config.HidePlayedInLatest; + user.EnableLocalPassword = config.EnableLocalPassword; + user.PlayDefaultAudioTrack = config.PlayDefaultAudioTrack; + user.DisplayCollectionsView = config.DisplayCollectionsView; + user.DisplayMissingEpisodes = config.DisplayMissingEpisodes; + user.AudioLanguagePreference = config.AudioLanguagePreference; + user.RememberAudioSelections = config.RememberAudioSelections; + user.EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay; + user.RememberSubtitleSelections = config.RememberSubtitleSelections; + user.SubtitleLanguagePreference = config.SubtitleLanguagePreference; + + // Only set cast receiver id if it is passed in and it exists in the server config. + if (!string.IsNullOrEmpty(config.CastReceiverId) + && _serverConfigurationManager.Configuration.CastReceiverApplications.Any(c => string.Equals(c.Id, config.CastReceiverId, StringComparison.Ordinal))) + { + user.CastReceiverId = config.CastReceiverId; + } + + user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews); + user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders); + user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes); + user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes); + + dbContext.Update(user); + _users[user.Id] = user; + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + } + + /// <inheritdoc/> + public async Task UpdatePolicyAsync(Guid userId, UserPolicy policy) + { + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + var user = dbContext.Users + .Include(u => u.Permissions) + .Include(u => u.Preferences) + .Include(u => u.AccessSchedules) + .Include(u => u.ProfileImage) + .FirstOrDefault(u => u.Id.Equals(userId)) + ?? throw new ArgumentException("No user exists with given Id!"); + + // The default number of login attempts is 3, but for some god forsaken reason it's sent to the server as "0" + int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch + { + -1 => null, + 0 => 3, + _ => policy.LoginAttemptsBeforeLockout + }; + + user.MaxParentalRatingScore = policy.MaxParentalRating; + user.MaxParentalRatingSubScore = policy.MaxParentalSubRating; + user.EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess; + user.RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit; + user.AuthenticationProviderId = policy.AuthenticationProviderId; + user.PasswordResetProviderId = policy.PasswordResetProviderId; + user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount; + user.LoginAttemptsBeforeLockout = maxLoginAttempts; + user.MaxActiveSessions = policy.MaxActiveSessions; + user.SyncPlayAccess = policy.SyncPlayAccess; + user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator); + user.SetPermission(PermissionKind.IsHidden, policy.IsHidden); + user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled); + user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl); + user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess); + user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement); + user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess); + user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback); + user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding); + user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding); + user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion); + user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading); + user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding); + user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion); + user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels); + user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices); + user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders); + user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers); + user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing); + user.SetPermission(PermissionKind.EnableCollectionManagement, policy.EnableCollectionManagement); + user.SetPermission(PermissionKind.EnableSubtitleManagement, policy.EnableSubtitleManagement); + user.SetPermission(PermissionKind.EnableLyricManagement, policy.EnableLyricManagement); + user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding); + user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing); + + user.AccessSchedules.Clear(); + foreach (var policyAccessSchedule in policy.AccessSchedules) + { + user.AccessSchedules.Add(policyAccessSchedule); + } + + // TODO: fix this at some point + user.SetPreference(PreferenceKind.BlockUnratedItems, policy.BlockUnratedItems ?? Array.Empty<UnratedItem>()); + user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags); + user.SetPreference(PreferenceKind.AllowedTags, policy.AllowedTags); + user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels); + user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices); + user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders); + user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders); + + dbContext.Update(user); + _users[user.Id] = user; + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + } + + /// <inheritdoc/> + public async Task ClearProfileImageAsync(User user) + { + if (user.ProfileImage is null) + { + return; + } + + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + dbContext.Remove(user.ProfileImage); + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + + user.ProfileImage = null; + _users[user.Id] = user; + } + + internal static void ThrowIfInvalidUsername(string name) + { + if (!string.IsNullOrWhiteSpace(name) && ValidUsernameRegex().IsMatch(name)) + { + return; + } + + throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)", nameof(name)); + } + + private IAuthenticationProvider GetAuthenticationProvider(User user) + { + return GetAuthenticationProviders(user)[0]; + } + + private IPasswordResetProvider GetPasswordResetProvider(User? user) + { + if (user is null) + { + return _defaultPasswordResetProvider; + } + + return GetPasswordResetProviders(user)[0]; + } + + private List<IAuthenticationProvider> GetAuthenticationProviders(User? user) + { + var authenticationProviderId = user?.AuthenticationProviderId; + + var providers = _authenticationProviders.Where(i => i.IsEnabled).ToList(); + + if (!string.IsNullOrEmpty(authenticationProviderId)) + { + providers = providers.Where(i => string.Equals(authenticationProviderId, i.GetType().FullName, StringComparison.OrdinalIgnoreCase)).ToList(); + } + + if (providers.Count == 0) + { + // Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found + _logger.LogWarning( + "User {Username} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected", + user?.Username, + user?.AuthenticationProviderId); + providers = new List<IAuthenticationProvider> + { + _invalidAuthProvider + }; + } + + return providers; + } + + private IPasswordResetProvider[] GetPasswordResetProviders(User user) + { + var passwordResetProviderId = user.PasswordResetProviderId; + var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray(); + + if (!string.IsNullOrEmpty(passwordResetProviderId)) + { + providers = providers.Where(i => + string.Equals(passwordResetProviderId, i.GetType().FullName, StringComparison.OrdinalIgnoreCase)) + .ToArray(); + } + + if (providers.Length == 0) + { + providers = new IPasswordResetProvider[] + { + _defaultPasswordResetProvider + }; + } + + return providers; + } + + private async Task<(IAuthenticationProvider? AuthenticationProvider, string Username, bool Success)> AuthenticateLocalUser( + string username, + string password, + User? user) + { + bool success = false; + IAuthenticationProvider? authenticationProvider = null; + + foreach (var provider in GetAuthenticationProviders(user)) + { + var providerAuthResult = + await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); + var updatedUsername = providerAuthResult.Username; + success = providerAuthResult.Success; + + if (success) + { + authenticationProvider = provider; + username = updatedUsername; + break; + } + } + + return (authenticationProvider, username, success); + } + + private async Task<(string Username, bool Success)> AuthenticateWithProvider( + IAuthenticationProvider provider, + string username, + string password, + User? resolvedUser) + { + try + { + var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser + ? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false) + : await provider.Authenticate(username, password).ConfigureAwait(false); + + if (authenticationResult.Username != username) + { + _logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username); + username = authenticationResult.Username; + } + + return (username, true); + } + catch (AuthenticationException ex) + { + _logger.LogDebug(ex, "Error authenticating with provider {Provider}", provider.Name); + + return (username, false); + } + } + + private async Task IncrementInvalidLoginAttemptCount(User user) + { + user.InvalidLoginAttemptCount++; + int? maxInvalidLogins = user.LoginAttemptsBeforeLockout; + if (maxInvalidLogins.HasValue && user.InvalidLoginAttemptCount >= maxInvalidLogins) + { + user.SetPermission(PermissionKind.IsDisabled, true); + await _eventManager.PublishAsync(new UserLockedOutEventArgs(user)).ConfigureAwait(false); + _logger.LogWarning( + "Disabling user {Username} due to {Attempts} unsuccessful login attempts.", + user.Username, + user.InvalidLoginAttemptCount); + } + + await UpdateUserAsync(user).ConfigureAwait(false); + } + + private async Task UpdateUserInternalAsync(JellyfinDbContext dbContext, User user) + { + dbContext.Users.Attach(user); + dbContext.Entry(user).State = EntityState.Modified; + _users[user.Id] = user; + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs b/Jellyfin.Server.Implementations/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs new file mode 100644 index 00000000..925b1351 --- /dev/null +++ b/Jellyfin.Server.Implementations/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// <autogenerated /> +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")] diff --git a/Jellyfin.Server.Implementations/obj/Debug/net10.0/Jellyfin.Server.Implementations.GeneratedMSBuildEditorConfig.editorconfig b/Jellyfin.Server.Implementations/obj/Debug/net10.0/Jellyfin.Server.Implementations.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 00000000..9f367a9a --- /dev/null +++ b/Jellyfin.Server.Implementations/obj/Debug/net10.0/Jellyfin.Server.Implementations.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,27 @@ +is_global = true +build_property.TargetFramework = net10.0 +build_property.TargetFramework = net10.0 +build_property.TargetFrameworkIdentifier = .NETCoreApp +build_property.TargetFrameworkIdentifier = .NETCoreApp +build_property.TargetFrameworkVersion = v10.0 +build_property.TargetFrameworkVersion = v10.0 +build_property.TargetPlatformMinVersion = +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = Jellyfin.Server.Implementations +build_property.ProjectDir = /srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 10.0 +build_property.EnableCodeStyleSeverity = diff --git a/Jellyfin.Server.Implementations/obj/Debug/net10.0/Jellyfin.Server.Implementations.assets.cache b/Jellyfin.Server.Implementations/obj/Debug/net10.0/Jellyfin.Server.Implementations.assets.cache new file mode 100644 index 0000000000000000000000000000000000000000..774ff49911c6c0a882dc3ca993b6aff2633712b1 GIT binary patch literal 26521 zcmd5^XPX<<6*l0tS&EGd&EU8J24j!5V?!wGb;*L)HeO6Z2spFS*dALNi5abJK}aDX zgpdRh2&6-LAieiWdM8hQN<QR6KIDC;=$;wf8I5GE*Lt4wNON@VIq#ggbMHOpmi>3_ z9=~Y8f(8E@e(siiuN=Cvy8GqV*4JJ=cEH*Az{9Kmed0go7QFJl|6Y9Gz6A>|gj}g` zv|6zWCy!Mf%PSbp>=CP8sF}s%<`G=mCEKf2J#(gPnT}a*JZ!mMVZ<0PwieWNsakhX zsOS|YcQnPvBXOftE}siS{G0>g@AE)UpOb01Vi(=2S3O)eiq%qJ+_ODn#&jI3WY%nD ze#96AA}yY&rjiLL@gY&kc3;VO&DMfk7=rA>_w(}ya$@?Z>DD^H`cz)5mQ%Oujl-^4 zu})Uq<Hf3L8Ooi519-f{ZQQxjSFtNnF+mktXD$kzxftIs<py}FZaJP^bv!(L=dgWb z)-~(6G-hn4WVwZW5S5aNn3C<j5@WhL>lUrumScs=MLj)VpnjnX)aB}tBerwIFlW5F zYZi&!OGS-pUT!!e#t7daRbzK5{Ch%mA=s+x$(O2MfPJP5u+`e>;@cN!$aLui0_FXg zZ@y=_HXIn$$ijdzIGXZ)PY0q=&68eX+s3AbZQ&EyUhyiXTaV0nMrF2)_v5H*nI$~K zh+h~q294oC?Rzmz1rZ3(n2ITg_!8X3A-E;@KAG)|dZTO=t2On48ncd9XM+qxLwHq3 zN5}M_SaJd-BLJSkMsSyX%8FH=r6HC}@Lf4`Sj5{V5dg%3)u4TuNKCKc6pvdCt5mKQ zj~BMW4MT;pJySqGv<-D-od4g&|M%d3dY(NFKg&R@7Rx~^Kr2B9Gi}Y-^~0uDw_KxW z7LTGv6o!mZysV+QF__zK*LMXX6M=}dcquH<;$<LOyd1PU(_+asi>_U_(Hx*}Q>J)l zJDUGV8&^OlZCnYWja8uinKta=?1)ns!@Fe+Y%$BVqoy&8iWJ@yDwtC;-4g0oVRGW& z$X;peDk!J1t3foz$K|v)W}qc>ys`~Xn)RyNf$e)@1tpZ%!L)?32G`p%p&T1>@chul zMYqc5cjr*-2G@pK*WsHtxE@r@)T#z6C9`fC-b1`i^j$7q(x5vH9gCVOn)n?Qo(_aX z9o-PxXP-&?*MO=y?Q8n>1Ox&S7<4_kz;bN@%XPSZGP^ld&d`9-c8Q^Lav&QQ9jdGd z8xt_E$Mxf<1Jkw2{*c2gckaFe&9V~W4GD;waQ)cnKuoV?IULzq>uyZo+l=cr9jAEG zYOoTvlASzgRXA!t)1LE|1V{tdkK}zdl$i))#i~@@Mw=(Bibd$A{9^Lq781AyaNXzv zmmH|(hb2o%RD%grL%4o6ca>B39Up}22DK?~uu>5pf~P&+;RN6jTz6FmV;+e|4IM%a zqXp3<%HV*_^C&2PTccc3vT7E>8K>B=onw|CB;<bNJDO!Dqq6eBk0tzg6RuzE;=yYm zcK2TE@TOIQtqJhkaQ$fK?~=k^!LYYoGp-(sf;z==(yccqklljo>>;zZg$$#^+z&en z|3n>peFDj?xbEgf388fNP$OJv4^jrEd{J)e&RvQ4wC(#FMWe!9GWaBt+d*FERjTIN z6^zSIATbsn)+IY!Rh#xVi^z&}2kdj)8jbMA@q14u9><!gpbm4mZj44_S2;}IyAuM4 z@Q{cRH@-bIG=Xp4_zuvmnTBfBa=C$sYD}5o5P3h@QuEGG^R7_yZhU8lHNNIxP#jaw zF>#<iiT(`<8<V(pGtmcQqFS-+nkS8!S*KLC4q1l+?l1=aaNwZMd((IC!H9kT#GV5z zXVwv<*p_$b#E`t@J3}~k;hWCf19~t&96ub$34yZG^i;z1UR-zJiJDtIVIy;9r1^r6 zIOc=a5)uw_$A0~dJh30w{IE`+M)j(dW~hN5S-pBvt%Pn_t!Rr`=_{t1sp4_3w%4lr zb#c5_qX0h)e3r!X3S=*9iT%mFK!xeN?DJ!2{#qGC)A5Kl_(PLA=O5=GIOX>Z3SgUD zHl<f7X&Gelrug@2^|vPeqr|362}3sd6yMlJJemxk?Dg5QDte;uHQB;J7(+qfAI&XA zQFi12DXkjoXndc%yA1+Ft8@?z5W{-CoE(f&Oh(^AofK<va0=j9nP8|&3)02Pf*RR0 z2aOaS)uPr2w}6rlM`y+#K+(k2S`*>;fdcy5(+x1v)rI{&1=eWoslJ#peSVMW|MGhu z{<=W*L%dv?$S;$3ZLVZ5C0EB9)MRHwiDfYfw)y0)ig7XAQLQ%rUK9JfwVkHS^68f3 zFWFbMvW<gbHkto(II=R{#s#_6KKyYvh}qKvAZAaeLHB?Tg6;*q5%ea|D%~VtFlQL4 zVS`IGOv>ui+z0-EdW3D-J0?jn0T?Og^c|J+TBrQY;LA5qGn1v7pm3ZQ>1Gaxy6GsL z?)$;NEKzB~`HM&e=Ow6u^Ab80ZvlUEt~wUZNJNS`r$EJ=Q_v}X0Q_~W6(E>J*fL`w z6^W^YNZW&<wnHh}-U>b+<Lb=u4+Q*NbWgMSrz*|)WSPyMgDk$VB(wP0<B^=%d!buq z?|YlI7e-{({!C`;1q7L`?=nj-w5>(m;%IRlXwI|q!miBDpV6$mz;JDN?`((8mTbI$ zw^6{$pBdAvO6XA*UO<ys_<7I13tTe$-c2GW%4nNnf+Tm6LO1R;1UQ*>pSNtgz$3Hm z^OR*5Xk?cC?8~kTpu^&|?5d8%qK#f;)de`2RnOmMsHj7wdwY{j7tmxj{p`-73(zu) zK9AXRfk<Z0&tTSEAdp$}ZeD=iXUhdRnJw?aJ>6!>1p=8RKjYbPf#VL*PW3J;E)2-5 z_-Ujog~pvBWM;#&!-JezaG^VyuAGbIQHy|^9~n@sz%`dgnV=a^5mW+Mpu?a!2)HnF z5h0NQ7m;w;ln%)e@UuOhGawVTWk5#TTu!C4eH8rO25bU^4A=;Qk6j1C2LDV3bONFb z=m?R^uXGTPf!}4oC#<c7mMGw(we=vKwd3H=c|a&|$$*f!xLiwzs|@}b4HyMR888y# zbtxDt;GZo4rGPI3O2WTB1-=7*{=wti1)Ku93^)mWQwsDd`12l+3fwXvC2lSU)75HD zkaaVb>V3c}z{`M@;EfdU4}m{#0j<C*16tzc@-dyWUGV2A;1y_Pz)Q44DQG?L&%S_I z+{iEhvPWSWjW@#OW;(am!SAY$_dH-0;AOx}@LYzb13wFX{x)&$0%`$W2GoSk<!L(T zC%`|u18#9sGT`P-aoL*grcQ!Cj{&(rECX^P=5jV2;#1&v5s`+`a_%f-G}|i>%7C2+ zxy((6umOHIFKF)rdI4Sr^aRi4Z#wV~gWrXFyAAjSLK*NAA(zGJ5IzF_8BYiZoOi&! zY^TRlaJ~)v-X#nKfbAiG34GI0Tt=sJ)T7{^MnXYYlnDh|+?`_a?ffru4mM}PLD-fF z2il%YvHcG4J6MF4TmjMTa=a)*a3{>OR@?<5OfJXMJ(PEXKgY}TB1ls(B_VO;mJZUp z!0*6wt&2PozRS6MFM_FCzNdrv7+^B*QqQblu{D>m<th<McNU>m1*>Lf-0Dee@7!|I z^eP1h8*ENkrsr98FWR`*T%Tx|9N+NsZjkipdvHy#kAq}Niv0J2o&dcM^d#tLuYi|r zXZF+<(~VGb4XMKFRnx$}A0TvK$tT#SKm_{%(6OF_9UBRqoEPo~0Ye9ue8T+@h;W|< z^&T!3=?8TpFWhGULkE|9!u>Faa6bY%-dpch?3&lHd!Gdk9bWQ@_c;*peiW2FBIw|q z3#x8q)+={Wf1d{u9b)o{_yrIVe+-nJ+v*x|$6o$8u;?(8Pt2bH5%VWO5B0cK(95pv z6;E`G`cpuoLrp$We;P#8p8?H<r@i5A1E)HC|6T+Z9cJ>0`6UoBe-<<s_4!oCUj7`A z=n#`n#GeNd@fSel-qwv;tvEJ<UHhFiabE-w9boba_)8!H{xYb?fV->5UjYyuVDbt0 zWe@?s0_t%W(oGfqDq!f~l25o_0}<}mK|LN#bY6SE0T4Q{<P+>SK?M6PP_Mym9qX{> zej6ZkV96)g?|=yQyP)jgvV*RpVit?oR9|j*b@p~C?L(=7;oJGJ%TIEA`91J-xXCB( z?}Lc@2cVvH9};(3y~srUL!i;2CZDK(1S0AmgL*a;s(SvIxEnA31UPhf$tT{Qf{6EL zAiGx$G4Xcg-Jb)64lVga`wI}!{t}dZpjHQU`K0a05t|A(5UQUKL;NHw@2|kq0VSV6 ze+?qg-+&JH%B>+#RSj}MeH9RNP{}9M-+~DBcc7m7RMdc6Kz|PaI-uke=pR4?`bSW2 zfvPq)7tlWefDS161o~$Xf&K-w5Hx5Qg((ZmBen-&Id+d$Dpg0vGm0LP5XTsY^Yd2_ zfBy|M3N4;nEZk{fhv;G3G1S3TI$lE4--V`qNliYuolddJgP07Q=i{GQ<|(x)0tp-c z2yOfm-|JvwXJw{g>@_Pq%SZf9lzL4d!_Ni(0xj7)yW6Vo4^GFMEUnaSby*>0?!|d| zIQ*;6KQd2ja%!`sWl^xDCGW1fMZs=lp<g`%JXCptTChh<FBv7^<@PuI&aH5(RdRpS zqTr~md|RXz1qW(n?Mdoyo^X}edD-Wm_;m4pvwn2a2_MI5^yyI=DZYn^J6BuxY4-WY zIIRs!F@x5!m6HX<R!%%coW^veu%otlQZ(4+c}en6rK`gNa<_K#P$lh#&>K@r#5a!m zTXL~K6t{vyeU^(`2~RZ2*amt{D4lsCQT9I3Euo6!?sv_ODtM=Z9i=ztS1ju)-63~A zYH+BXU0d_pP=#ngzB?KV)4_30+-N$I-$Ym{p<YeK>Q?A)OHrR)inowwKkIOA?dmj~ z9Q$8~+N+JVS3oN_>xp}diOFhkdxs`uS$ZdjUt_k_2{E$rDWsa6v}u(uvk<X$w^w&( zTaRck#&-MCcCOvNX?0$AXU+3f^qoxD9c^w4KI*q}J-bLa`PAE+2|GQyqTT-D=!l7A z%c#7d_99bXuoro8c)Z8D@Q!7N5kKCe&)=tvUJ$2rtPG`Nr*w#o%u#>Uddx=l2IWP; zQ56FkFtHuT$<s1gH#J}1P6c;9`)-XXZk_cq#I3dyg;y71jAtzlhhQrvy~#?gY!~f% zxv|5-K-DVs?J~=rb^Za%tCmk#rG@vnv(`cmDh4w{BU5ww<V~3+(vMM|$}CrD9!h~R zaE?QevnRimHr>H|Eqm>TGofJ`PjgD^WOkWK{)1LETiBQQ!@=fO<=e7twH%z9*y-Fq z&Y8!18Z~QiI6v8s(^4BaFw`tBtfM9@-(h)mo9WuBGd(-Qda=B9V0o!_)VL%*GVE`T z_pZ<#0K&SzI2^KFsrJ3glRF@>PW_`h;Q|sywnR&lmn|M2_Q<|1eb@sKTZC5C-suJ~ zylolcv`VX?#dUhEv5pw(hI5D|-H-w}rdsv!o3$2n{U_jT%PpJ)CEcz3qRce=&b3+- zQ7@PJJR*UlUA;Qd_36is@$PI%ytnEPPSpUGCBZKaj`{Et9Z=%frAuQ?eAGv5h;cQ2 z1oUQ{pfTFjT7*Txq|N2=7Ga0&;@CbMAh{{O(q#vNwGfjy_^)mswk`J}xhUd>rlxi) z4pya0_BYGk*eay5bm&Y~TFI+koh5r=Q{~u*MrrEIy+)~$RZ6Y$=7jak`SsPA!WV@z z-u?`|krd6I2Vc`hd`YM~)H0AFp6cXvZ3|oyw=gy`q$`p=H@6{25o8Ovq{Uws5Ngxj zP1dBE_HJSY<lXpxXrG73$R;YK_IU_#`8>ehlDP5gIoH=^OJ5v#DAbWuo&5xosuR+o z>X4g!p<*6PI3)Q(#bm=5%I76p65+Ij%4AC-B*T^joECK|H{DL1l8n@C9UE!7Ue0{A znXy~nzR<E3b?Wtze~EjH!T0Cb)@tpDB1;phRO6LnMohW8MYzAoQ9VlAIjbw0XcMA~ z!|Q9C0$DRkN=-s)ad^EksYW$&svBYFn=aSioGFT960VJ@ienN|J|<iz)To-4rAA2w qYPOCoZO&Z;Ga!o*w+|wxx}wQf`Si6-UdrTyUl_-9Dy6|x!~X$cJdC;k literal 0 HcmV?d00001 diff --git a/Jellyfin.Server.Implementations/obj/Debug/net10.0/Jellyfin.Server.Implementations.csproj.AssemblyReference.cache b/Jellyfin.Server.Implementations/obj/Debug/net10.0/Jellyfin.Server.Implementations.csproj.AssemblyReference.cache new file mode 100644 index 0000000000000000000000000000000000000000..24cda7dba01da01ba9f955efa2f17fe0d67854a0 GIT binary patch literal 7784 zcmds+d0Z3M7RNIn0`3YH5vV9afhUtaBq&-ya6<&d3au=YnTZ6#1hb$hg19^p1cjp5 zDt%C_il87W?kM0Itq~QeR;|0D)z>FV@jbO~W|A<ZN=OTTG5I7D=AM(}cfRM`d*{#> z7)GPffLZidX#z@L7$?P0IUU55^HBvo5rX5P`S{<EJQ;)I(PR{nNa1)oA7p?mx<o9b zW2iy^GUz_U_zYq^5FZB-i39-fPo=Sb8;})y+X4W9LHZ_u1u={tCYlqCG{@fqY4}T@ zW%wKXOaqFD*Rh%_`S{zuggyRu=xiS*tq)-Z(9-YRTkx)a#R6bi<F`jA$=!1l0acDS zUJ7@lA1<j^d+^Sb^KL%^=eEvRR8cr$U<(qQvNqzNU)IlF<=}6e1;x+Dn~d9U|Ko5w z?rMwqCkq4<^DLgdyu@<MuuRKc_25#KT|ny?i&wYzk7_bc9r#Vff31AgeJ?ECrCi+> zb9hVWr1<TmmeaSIt=$d}rUN1RLdQ9Nfy(4!DdtTE4P=aS^ixU{N*U^np-P1el8kc< zRthCzB79-ecog%7c?g0Em>iZs2r)z~5fK970OlBEBor$|kX(VvKp28!#n^m03*>?v z9VQ-*$yclh(3tpXOej*G_C8yxZxN)1eqaR<K{vync)-b{8?au7Ph);%(&&8E9C-2Y zr)MSK#f2nIeo`^1IK=1a0kfQ>nq@gW^^5FObGt9gJCpH5y7<Ff4c<8=vZzdKqQ zd1ZXiB%7^uS%6E<qdh}*rH<}fw(gz%lKa-oywH?~tpY(*K$!(P;z@d<1Z#e_@i!N* zlAw@K^_Rc4&b<o1R|{1nEVMrUWKIy@6bSo3E5(b9o-w?5T$s%h!W=%zLD_r;TN@-A zFvTD*L=3?)u|f>X=^T(rfg<W@a{CxXXxmb+SD|Dr^`nLX0ttabho=3@_3B7lO+J(( zrIY+}V{~FliKPAGgAHmE!%7ZD=P__y3=c?>7z=@HE>D+=as`ATkPKnrN~ihF;RI14 zlXDpvP1lJuG$TiV3A%68p1YBH4X!}U=DjJt=XN^#)i`d2uDXT2281i}ez0qlg;j2^ zbx4hlOrKMIUOvvAwkYm`>vEII{J~C(rx)aBdR8y+w1PZrRP}@3938UAiL>3~qLa&+ z*x0C)du6k^*X+kU8Z&*#i^j~oKOT14mz{c~W?uf$_2F2~Zf=f!ZlndB_XfI0rry8z zq~y#R<Yos<BlS@j@L-XTuIC3-BxHKRc(#ccC`-T;>LLVST{8jk83&U~#P~D;DWtN$ zfGAAQks@}bg{38CtzoHe*n0tne@t$V@)Q{48OOr#86M9WU0D$`{B%}_Vj652zg1IA zrucPo7T4tpaqdPb71}oC`L8X-O3!6ZT{Aw`)MVK6Z0n7iZXge@CR`9)JHcU{UF+e| z=g#^;wv7P4tn7sR#aqecPyhAObu6%TpY@}2XG@z}r(RxFTl}h|`HPX!=_XGc*W7ip zU07T`TmGi8aE!%_t-K4~X_6^6qr*-fe=K}g`To~B=QDwpjGu$<UM?a_sU?5PwSrP{ z1rUI%qXJLH#F+U^#1?Qko(NCi2?=-th+1c1gK<v~!!oH{DpG(5q<}#A0-~BgSSmy5 zOrnC>Iv)6fPoLRtJee?_tub!jA6+rEnlO)?(}#c566mJ|W~x1RgISB0Tc25_O`o6J zJKF2(hbb%f$<7A#&m1)JLC*Zrw$|H)(P`eH<xUSWPmKWnb8|z#n)MDPGu^)%zw>A7 zkv3;iTTY?3q8^&f$W_BfCd3__BrEf(X!aglU&qNxnpIJ`=Y-t*p>@RWvVd^YmB$Me zC-0>r2Y$Rar7icc`5ODe)$W&%kj!=+O6I%W+8qN#d{i$*Q4>mIFhYn>;3;79Spo!s zP#!}YDH<R(5+jN!#EN8*3`#&3N@ejMAxZq04k-OehX9>}AC8`y59=HqwMZQ(i68Ok zy=WnWx_cDy8sP6U1|gvwUkzaezB!lwsdSA1?$g1WqCaZVAT51&gHZrGy7%5xz>E(< zkLJay?&O6Gl|V#NK$5?n6`}3ER8F|>bkpX9tKo1^&b>DyB*uoINAqEMck-e072b$x z;?FDo$^qlF$uQP%Bs7k_HxXiuk>8_9u+-SKVvz#Hh?b2UM9@SOLr@G(PLLvKSMveo zH@V$!CgDfT1zR7#;%PQ~9r1VS*p8pr)DU~XUbFw}h`&?$f5MONt-HQE?sPjCXb?Y! z3KMX?M8g8)>UQuQ8x=wRxB;lY*PCdHZf_==X8ovPvh%6qM0Cw`hkk2Q*OH+eJE+zC zjIPD%wPd^v3QUwrB*}Q^mPg4WZWx1!%T#_wKF!T`CvQm1$PseWt`k6<b;9(;07b;u zSi^nV;TE>{r_CyG@$x#>kP|gvN@b8_(Cx|FPB{ec_rGd-@mb0dRzvoYqqiI~b}U^Y zcZ?qvf0t7kvxT>F!d{2V4^joJuZOr=9&Adpr86_2Oy5T#wL!<t+Fl=sE4B0c#J7r5 z@O*i7B%Ex2qr|~?`S;h(m({;F_ph!N5AaDTYc1w%A+?+>`J=wA&G`Z_mDESYoM_MD zkFH}5^C7lK1hYka5ue3{VL~}^bkXi3<FC0*)Pq3WC2An(=N3hxA2r;2am=Jd_9PRT z!J5#l?1`{M1}y}IN(_;p(P*^hDjRRE;L`!~V{^gmkU;#NKiDrYl<<QTcsEUq%A=E5 zR3X`)t*eYArv}?jt;voIQBeKy=S2KgzZ=EX&~hold0^j+@{tcV-48Deh*XDpg+lK6 z_qY7=`*ZZr;fQy4oz}Uy*T%W`=YBHgvs*V*Z{KY?VWnQ0JEE!~r1dk^g^1>hoFAU3 z18!GU51o^%vS=*WrTUasa>zs4uj)#{gQXis`A)DJ7<B$0OX^0lk17VPO#0&|>v=A7 z<pt^!X?bL3E_Znv*|J2{48(M*nS!ecFUaDtSX~z#pT**#5El`#J%uoY3W$Ou4j{^S zz~qg+kx&U+3TF1Iby}E1^@I^tJqgj&nPZa64xRa^Vf*eMocF6L_eSM?zxh?&kSOKy zd^2~I>iYE=b9}Km_4jR};bP9?w8cg5v)DHuD^06hKexU4%Cgqxo9`o5h_boQ)>|c> z{`7QZmdm#*Yff)EH?U$!bX4*FWLLk^w-YTbE8pIHYrY|2h5fdIwomqbKA>)fjn(F? zV<bfDj61HX#rwHH_y=15COjsG&qo<7J`8z6LN1%DL5IWp3)Pt@lO~A~{GVH$)hDr- z>HXjjJt&}?%QtF)j_SgvC-JUM@yk>?6patJu@8(J>qZZ{vz;?y$5+EeyBC&wJ5IY- zn$ozg_*mbGWwlQqn*xQqk+o(mF&pHxZ_hja^5$!ojH|`XU|B`Y|GaC4T?bVm^LI&I z)+ebG%9JO|PuUfj&z3bss2kEYJaZ15PTMr=Qj1`0ZQg5DdX&%W&yK4bhTb6YZeci7 g|MSwZVSuO$<$n{l0A@3oh>*wUAq*kHWa(i4AEim(8UO$Q literal 0 HcmV?d00001 diff --git a/Jellyfin.Server.Implementations/obj/Jellyfin.Server.Implementations.csproj.nuget.dgspec.json b/Jellyfin.Server.Implementations/obj/Jellyfin.Server.Implementations.csproj.nuget.dgspec.json new file mode 100644 index 00000000..afc28999 --- /dev/null +++ b/Jellyfin.Server.Implementations/obj/Jellyfin.Server.Implementations.csproj.nuget.dgspec.json @@ -0,0 +1,5260 @@ +{ + "format": 1, + "restore": { + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj": {} + }, + "projects": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj", + "projectName": "Jellyfin.Naming", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "projectName": "Jellyfin.Data", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Logging": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj", + "projectName": "Jellyfin.Server.Implementations", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "AsyncKeyedLock": { + "target": "Package", + "version": "[8.0.2, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Relational": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj", + "projectName": "Jellyfin.Common", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.AspNetCore": "(,10.0.32767]", + "Microsoft.AspNetCore.Antiforgery": "(,10.0.32767]", + "Microsoft.AspNetCore.App": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.BearerToken": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Cookies": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.OAuth": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization.Policy": "(,10.0.32767]", + "Microsoft.AspNetCore.Components": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Endpoints": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Forms": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Server": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Web": "(,10.0.32767]", + "Microsoft.AspNetCore.Connections.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.CookiePolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.Internal": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.AspNetCore.HostFiltering": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Html.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Features": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Results": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpLogging": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpOverrides": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpsPolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Identity": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Metadata": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ApiExplorer": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.DataAnnotations": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Xml": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.RazorPages": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.TagHelpers": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ViewFeatures": "(,10.0.32767]", + "Microsoft.AspNetCore.OutputCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.RateLimiting": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor.Runtime": "(,10.0.32767]", + "Microsoft.AspNetCore.RequestDecompression": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCompression": "(,10.0.32767]", + "Microsoft.AspNetCore.Rewrite": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.HttpSys": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IIS": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IISIntegration": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets": "(,10.0.32767]", + "Microsoft.AspNetCore.Session": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticAssets": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticFiles": "(,10.0.32767]", + "Microsoft.AspNetCore.WebSockets": "(,10.0.32767]", + "Microsoft.AspNetCore.WebUtilities": "(,10.0.32767]", + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.Extensions.Caching.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Caching.Memory": "(,10.0.32767]", + "Microsoft.Extensions.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Binder": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.CommandLine": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.FileExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Ini": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Json": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.KeyPerFile": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.UserSecrets": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Xml": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Features": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Composite": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Physical": "(,10.0.32767]", + "Microsoft.Extensions.FileSystemGlobbing": "(,10.0.32767]", + "Microsoft.Extensions.Hosting": "(,10.0.32767]", + "Microsoft.Extensions.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Http": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Core": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Stores": "(,10.0.32767]", + "Microsoft.Extensions.Localization": "(,10.0.32767]", + "Microsoft.Extensions.Localization.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Console": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Debug": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventLog": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventSource": "(,10.0.32767]", + "Microsoft.Extensions.Logging.TraceSource": "(,10.0.32767]", + "Microsoft.Extensions.ObjectPool": "(,10.0.32767]", + "Microsoft.Extensions.Options": "(,10.0.32767]", + "Microsoft.Extensions.Options.ConfigurationExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Options.DataAnnotations": "(,10.0.32767]", + "Microsoft.Extensions.Primitives": "(,10.0.32767]", + "Microsoft.Extensions.Validation": "(,10.0.32767]", + "Microsoft.Extensions.WebEncoders": "(,10.0.32767]", + "Microsoft.JSInterop": "(,10.0.32767]", + "Microsoft.Net.Http.Headers": "(,10.0.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.EventLog": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Cbor": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Cryptography.Xml": "(,10.0.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.RateLimiting": "(,10.0.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj", + "projectName": "Jellyfin.Controller", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "BitFaster.Caching": { + "target": "Package", + "version": "[2.5.4, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Configuration.Binder": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj", + "projectName": "Jellyfin.Model", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "MimeTypes": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[2.5.2, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.AspNetCore": "(,10.0.32767]", + "Microsoft.AspNetCore.Antiforgery": "(,10.0.32767]", + "Microsoft.AspNetCore.App": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.BearerToken": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Cookies": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.OAuth": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization.Policy": "(,10.0.32767]", + "Microsoft.AspNetCore.Components": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Endpoints": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Forms": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Server": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Web": "(,10.0.32767]", + "Microsoft.AspNetCore.Connections.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.CookiePolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.Internal": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.AspNetCore.HostFiltering": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Html.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Features": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Results": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpLogging": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpOverrides": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpsPolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Identity": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Metadata": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ApiExplorer": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.DataAnnotations": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Xml": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.RazorPages": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.TagHelpers": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ViewFeatures": "(,10.0.32767]", + "Microsoft.AspNetCore.OutputCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.RateLimiting": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor.Runtime": "(,10.0.32767]", + "Microsoft.AspNetCore.RequestDecompression": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCompression": "(,10.0.32767]", + "Microsoft.AspNetCore.Rewrite": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.HttpSys": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IIS": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IISIntegration": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets": "(,10.0.32767]", + "Microsoft.AspNetCore.Session": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticAssets": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticFiles": "(,10.0.32767]", + "Microsoft.AspNetCore.WebSockets": "(,10.0.32767]", + "Microsoft.AspNetCore.WebUtilities": "(,10.0.32767]", + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.Extensions.Caching.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Caching.Memory": "(,10.0.32767]", + "Microsoft.Extensions.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Binder": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.CommandLine": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.FileExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Ini": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Json": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.KeyPerFile": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.UserSecrets": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Xml": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Features": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Composite": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Physical": "(,10.0.32767]", + "Microsoft.Extensions.FileSystemGlobbing": "(,10.0.32767]", + "Microsoft.Extensions.Hosting": "(,10.0.32767]", + "Microsoft.Extensions.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Http": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Core": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Stores": "(,10.0.32767]", + "Microsoft.Extensions.Localization": "(,10.0.32767]", + "Microsoft.Extensions.Localization.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Console": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Debug": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventLog": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventSource": "(,10.0.32767]", + "Microsoft.Extensions.Logging.TraceSource": "(,10.0.32767]", + "Microsoft.Extensions.ObjectPool": "(,10.0.32767]", + "Microsoft.Extensions.Options": "(,10.0.32767]", + "Microsoft.Extensions.Options.ConfigurationExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Options.DataAnnotations": "(,10.0.32767]", + "Microsoft.Extensions.Primitives": "(,10.0.32767]", + "Microsoft.Extensions.Validation": "(,10.0.32767]", + "Microsoft.Extensions.WebEncoders": "(,10.0.32767]", + "Microsoft.JSInterop": "(,10.0.32767]", + "Microsoft.Net.Http.Headers": "(,10.0.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.EventLog": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Cbor": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Cryptography.Xml": "(,10.0.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.RateLimiting": "(,10.0.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "projectName": "Jellyfin.CodeAnalysis", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "netstandard2.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "netstandard2.0": { + "targetAlias": "netstandard2.0", + "projectReferences": {} + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "netstandard2.0": { + "targetAlias": "netstandard2.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[3.11.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.CSharp": { + "suppressParent": "All", + "target": "Package", + "version": "[5.0.0, )", + "versionCentrallyManaged": true + }, + "NETStandard.Library": { + "suppressParent": "All", + "target": "Package", + "version": "[2.0.3, )", + "autoReferenced": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/RuntimeIdentifierGraph.json" + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "version": "10.11.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "projectName": "Jellyfin.Database.Implementations", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Design": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Relational": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Tools": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Polly": { + "target": "Package", + "version": "[8.6.5, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj", + "projectName": "Jellyfin.Database.Providers.Sqlite", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Design": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Relational": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Sqlite": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Tools": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "projectName": "Jellyfin.Extensions", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "noWarn": [ + "NU5104" + ], + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "Diacritics": { + "target": "Package", + "version": "[4.1.4, )", + "versionCentrallyManaged": true + }, + "ICU4N.Transliterator": { + "target": "Package", + "version": "[60.1.0-alpha.356, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj": { + "version": "10.11.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj", + "projectName": "Jellyfin.MediaEncoding.Keyframes", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "NEbml": { + "target": "Package", + "version": "[1.1.0.5, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + } + } +} \ No newline at end of file diff --git a/Jellyfin.Server.Implementations/obj/Jellyfin.Server.Implementations.csproj.nuget.g.props b/Jellyfin.Server.Implementations/obj/Jellyfin.Server.Implementations.csproj.nuget.g.props new file mode 100644 index 00000000..aa204e43 --- /dev/null +++ b/Jellyfin.Server.Implementations/obj/Jellyfin.Server.Implementations.csproj.nuget.g.props @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess> + <RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool> + <ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile> + <NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageRoot> + <NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageFolders> + <NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle> + <NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion> + </PropertyGroup> + <ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <SourceRoot Include="/home/wjones/.nuget/packages/" /> + </ItemGroup> + <ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props')" /> + <Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props')" /> + </ImportGroup> + <PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <PkgStyleCop_Analyzers_Unstable Condition=" '$(PkgStyleCop_Analyzers_Unstable)' == '' ">/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556</PkgStyleCop_Analyzers_Unstable> + <PkgSmartAnalyzers_MultithreadingAnalyzer Condition=" '$(PkgSmartAnalyzers_MultithreadingAnalyzer)' == '' ">/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31</PkgSmartAnalyzers_MultithreadingAnalyzer> + <PkgSerilogAnalyzer Condition=" '$(PkgSerilogAnalyzer)' == '' ">/home/wjones/.nuget/packages/seriloganalyzer/0.15.0</PkgSerilogAnalyzer> + <PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers Condition=" '$(PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers)' == '' ">/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0</PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers> + <PkgIDisposableAnalyzers Condition=" '$(PkgIDisposableAnalyzers)' == '' ">/home/wjones/.nuget/packages/idisposableanalyzers/4.0.8</PkgIDisposableAnalyzers> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/Jellyfin.Server.Implementations/obj/Jellyfin.Server.Implementations.csproj.nuget.g.targets b/Jellyfin.Server.Implementations/obj/Jellyfin.Server.Implementations.csproj.nuget.g.targets new file mode 100644 index 00000000..b11cc55a --- /dev/null +++ b/Jellyfin.Server.Implementations/obj/Jellyfin.Server.Implementations.csproj.nuget.g.targets @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> + <Import Project="$(NuGetPackageRoot)sqlitepclraw.lib.e_sqlite3/2.1.11/buildTransitive/net9.0/SQLitePCLRaw.lib.e_sqlite3.targets" Condition="Exists('$(NuGetPackageRoot)sqlitepclraw.lib.e_sqlite3/2.1.11/buildTransitive/net9.0/SQLitePCLRaw.lib.e_sqlite3.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.binder/10.0.3/buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.binder/10.0.3/buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets')" /> + <Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets')" /> + </ImportGroup> +</Project> \ No newline at end of file diff --git a/Jellyfin.Server.Implementations/obj/project.assets.json b/Jellyfin.Server.Implementations/obj/project.assets.json new file mode 100644 index 00000000..d5a97152 --- /dev/null +++ b/Jellyfin.Server.Implementations/obj/project.assets.json @@ -0,0 +1,2342 @@ +{ + "version": 3, + "targets": { + "net10.0": { + "AsyncKeyedLock/8.0.2": { + "type": "package", + "compile": { + "lib/net9.0/AsyncKeyedLock.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/AsyncKeyedLock.dll": { + "related": ".xml" + } + } + }, + "BitFaster.Caching/2.5.4": { + "type": "package", + "compile": { + "lib/net6.0/BitFaster.Caching.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/BitFaster.Caching.dll": { + "related": ".xml" + } + } + }, + "Diacritics/4.1.4": { + "type": "package", + "compile": { + "lib/net9.0/Diacritics.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Diacritics.dll": { + "related": ".xml" + } + } + }, + "ICU4N/60.1.0-alpha.356": { + "type": "package", + "dependencies": { + "J2N": "2.0.0", + "Microsoft.Extensions.Caching.Memory": "2.0.0" + }, + "compile": { + "lib/netstandard2.0/ICU4N.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/ICU4N.dll": { + "related": ".xml" + } + } + }, + "ICU4N.Transliterator/60.1.0-alpha.356": { + "type": "package", + "dependencies": { + "ICU4N": "60.1.0-alpha.356" + }, + "compile": { + "lib/netstandard2.0/ICU4N.Transliterator.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/ICU4N.Transliterator.dll": { + "related": ".xml" + } + } + }, + "IDisposableAnalyzers/4.0.8": { + "type": "package" + }, + "J2N/2.0.0": { + "type": "package", + "compile": { + "lib/net6.0/J2N.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/J2N.dll": { + "related": ".xml" + } + } + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers/4.14.0": { + "type": "package", + "build": { + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props": {}, + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets": {} + } + }, + "Microsoft.Data.Sqlite.Core/10.0.3": { + "type": "package", + "dependencies": { + "SQLitePCLRaw.core": "2.1.11" + }, + "compile": { + "lib/net8.0/Microsoft.Data.Sqlite.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Microsoft.Data.Sqlite.dll": { + "related": ".xml" + } + } + }, + "Microsoft.EntityFrameworkCore/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "10.0.3", + "Microsoft.EntityFrameworkCore.Analyzers": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props": {} + } + }, + "Microsoft.EntityFrameworkCore.Abstractions/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + } + }, + "Microsoft.EntityFrameworkCore.Analyzers/10.0.3": { + "type": "package" + }, + "Microsoft.EntityFrameworkCore.Relational/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + } + }, + "Microsoft.EntityFrameworkCore.Sqlite/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore.Sqlite.Core": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.DependencyModel": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.11", + "SQLitePCLRaw.core": "2.1.11" + }, + "compile": { + "lib/net10.0/_._": {} + }, + "runtime": { + "lib/net10.0/_._": {} + } + }, + "Microsoft.EntityFrameworkCore.Sqlite.Core/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Data.Sqlite.Core": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.DependencyModel": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "SQLitePCLRaw.core": "2.1.11" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Sqlite.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Sqlite.dll": { + "related": ".xml" + } + } + }, + "Microsoft.Extensions.Caching.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Caching.Memory/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Caching.Memory.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Caching.Memory.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Configuration/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Configuration.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Configuration.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Configuration.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Configuration.Binder/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Configuration.Binder.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Configuration.Binder.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets": {} + } + }, + "Microsoft.Extensions.DependencyInjection/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.DependencyModel/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.Extensions.DependencyModel.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.DependencyModel.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Logging/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Logging.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Logging.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Logging.Abstractions/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets": {} + } + }, + "Microsoft.Extensions.Options/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Options.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Options.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/Microsoft.Extensions.Options.targets": {} + } + }, + "Microsoft.Extensions.Primitives/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.Extensions.Primitives.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Primitives.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "NEbml/1.1.0.5": { + "type": "package", + "compile": { + "lib/netstandard2.0/NEbml.Core.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/NEbml.Core.dll": { + "related": ".xml" + } + } + }, + "Polly/8.6.5": { + "type": "package", + "dependencies": { + "Polly.Core": "8.6.5" + }, + "compile": { + "lib/net6.0/Polly.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net6.0/Polly.dll": { + "related": ".pdb;.xml" + } + } + }, + "Polly.Core/8.6.5": { + "type": "package", + "compile": { + "lib/net8.0/Polly.Core.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net8.0/Polly.Core.dll": { + "related": ".pdb;.xml" + } + } + }, + "SerilogAnalyzer/0.15.0": { + "type": "package" + }, + "SmartAnalyzers.MultithreadingAnalyzer/1.1.31": { + "type": "package" + }, + "SQLitePCLRaw.bundle_e_sqlite3/2.1.11": { + "type": "package", + "dependencies": { + "SQLitePCLRaw.lib.e_sqlite3": "2.1.11", + "SQLitePCLRaw.provider.e_sqlite3": "2.1.11" + }, + "compile": { + "lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll": {} + }, + "runtime": { + "lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll": {} + } + }, + "SQLitePCLRaw.core/2.1.11": { + "type": "package", + "compile": { + "lib/netstandard2.0/SQLitePCLRaw.core.dll": {} + }, + "runtime": { + "lib/netstandard2.0/SQLitePCLRaw.core.dll": {} + } + }, + "SQLitePCLRaw.lib.e_sqlite3/2.1.11": { + "type": "package", + "compile": { + "lib/netstandard2.0/_._": {} + }, + "runtime": { + "lib/netstandard2.0/_._": {} + }, + "build": { + "buildTransitive/net9.0/SQLitePCLRaw.lib.e_sqlite3.targets": {} + }, + "runtimeTargets": { + "runtimes/browser-wasm/nativeassets/net9.0/e_sqlite3.a": { + "assetType": "native", + "rid": "browser-wasm" + }, + "runtimes/linux-arm/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-arm" + }, + "runtimes/linux-arm64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-arm64" + }, + "runtimes/linux-armel/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-armel" + }, + "runtimes/linux-mips64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-mips64" + }, + "runtimes/linux-musl-arm/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-arm" + }, + "runtimes/linux-musl-arm64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-arm64" + }, + "runtimes/linux-musl-riscv64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-riscv64" + }, + "runtimes/linux-musl-s390x/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-s390x" + }, + "runtimes/linux-musl-x64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-x64" + }, + "runtimes/linux-ppc64le/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-ppc64le" + }, + "runtimes/linux-riscv64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-riscv64" + }, + "runtimes/linux-s390x/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-s390x" + }, + "runtimes/linux-x64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-x64" + }, + "runtimes/linux-x86/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-x86" + }, + "runtimes/maccatalyst-arm64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "maccatalyst-arm64" + }, + "runtimes/maccatalyst-x64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "maccatalyst-x64" + }, + "runtimes/osx-arm64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "osx-arm64" + }, + "runtimes/osx-x64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "osx-x64" + }, + "runtimes/win-arm/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-arm" + }, + "runtimes/win-arm64/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-arm64" + }, + "runtimes/win-x64/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-x64" + }, + "runtimes/win-x86/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-x86" + } + } + }, + "SQLitePCLRaw.provider.e_sqlite3/2.1.11": { + "type": "package", + "dependencies": { + "SQLitePCLRaw.core": "2.1.11" + }, + "compile": { + "lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll": {} + }, + "runtime": { + "lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll": {} + } + }, + "StyleCop.Analyzers/1.2.0-beta.556": { + "type": "package", + "dependencies": { + "StyleCop.Analyzers.Unstable": "1.2.0.556" + } + }, + "StyleCop.Analyzers.Unstable/1.2.0.556": { + "type": "package" + }, + "Jellyfin.CodeAnalysis/1.0.0": { + "type": "project", + "framework": ".NETStandard,Version=v2.0", + "compile": { + "bin/placeholder/Jellyfin.CodeAnalysis.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.CodeAnalysis.dll": {} + } + }, + "Jellyfin.Common/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Common.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Common.dll": {} + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Jellyfin.Controller/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "BitFaster.Caching": "2.5.4", + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.MediaEncoding.Keyframes": "10.11.0", + "Jellyfin.Model": "10.12.0", + "Jellyfin.Naming": "10.12.0", + "Microsoft.Extensions.Configuration.Binder": "10.0.3" + }, + "compile": { + "bin/placeholder/Jellyfin.Controller.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Controller.dll": {} + } + }, + "Jellyfin.Data/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Database.Implementations": "10.11.0", + "Microsoft.Extensions.Logging": "10.0.3" + }, + "compile": { + "bin/placeholder/Jellyfin.Data.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Data.dll": {} + } + }, + "Jellyfin.Database.Implementations/10.11.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Polly": "8.6.5" + }, + "compile": { + "bin/placeholder/Jellyfin.Database.Implementations.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Database.Implementations.dll": {} + } + }, + "Jellyfin.Database.Providers.Sqlite/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Database.Implementations": "10.11.0", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3" + }, + "compile": { + "bin/placeholder/Jellyfin.Database.Providers.Sqlite.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Database.Providers.Sqlite.dll": {} + } + }, + "Jellyfin.Extensions/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Diacritics": "4.1.4", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "Jellyfin.CodeAnalysis": "1.0.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Extensions.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Extensions.dll": {} + } + }, + "Jellyfin.MediaEncoding.Keyframes/10.11.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "NEbml": "1.1.0.5" + }, + "compile": { + "bin/placeholder/Jellyfin.MediaEncoding.Keyframes.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.MediaEncoding.Keyframes.dll": {} + } + }, + "Jellyfin.Model/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Data": "10.12.0", + "Jellyfin.Extensions": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Model.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Model.dll": {} + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Jellyfin.Naming/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Naming.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Naming.dll": {} + } + } + } + }, + "libraries": { + "AsyncKeyedLock/8.0.2": { + "sha512": "QGys5cnIerNryv7V14PDkvGnlLz69kJtTfdnr+Lndcu+lRre397RNyU4FIeAJWgI9u73lTzXL52Qca9B/ncLXw==", + "type": "package", + "path": "asynckeyedlock/8.0.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "asynckeyedlock.8.0.2.nupkg.sha512", + "asynckeyedlock.nuspec", + "lib/net5.0/AsyncKeyedLock.dll", + "lib/net5.0/AsyncKeyedLock.xml", + "lib/net6.0/AsyncKeyedLock.dll", + "lib/net6.0/AsyncKeyedLock.xml", + "lib/net7.0/AsyncKeyedLock.dll", + "lib/net7.0/AsyncKeyedLock.xml", + "lib/net8.0/AsyncKeyedLock.dll", + "lib/net8.0/AsyncKeyedLock.xml", + "lib/net9.0/AsyncKeyedLock.dll", + "lib/net9.0/AsyncKeyedLock.xml", + "lib/netstandard2.0/AsyncKeyedLock.dll", + "lib/netstandard2.0/AsyncKeyedLock.xml", + "lib/netstandard2.1/AsyncKeyedLock.dll", + "lib/netstandard2.1/AsyncKeyedLock.xml", + "logo.png" + ] + }, + "BitFaster.Caching/2.5.4": { + "sha512": "1QroTY1PVCZOSG9FnkkCrmCKk/+bZCgI/YXq376HnYwUDJ4Ho0EaV4YaA/5v5WYLnwIwIO7RZkdWbg9pxIpueQ==", + "type": "package", + "path": "bitfaster.caching/2.5.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "ReadMe.md", + "bitfaster.caching.2.5.4.nupkg.sha512", + "bitfaster.caching.nuspec", + "lib/net6.0/BitFaster.Caching.dll", + "lib/net6.0/BitFaster.Caching.xml", + "lib/netcoreapp3.1/BitFaster.Caching.dll", + "lib/netcoreapp3.1/BitFaster.Caching.xml", + "lib/netstandard2.0/BitFaster.Caching.dll", + "lib/netstandard2.0/BitFaster.Caching.xml" + ] + }, + "Diacritics/4.1.4": { + "sha512": "9JK2bI+oWnzOK4Lu+5FXt7czofD28AMnZ95np5fkSClqioPnga1JVpjaUxVJaEpc/5HIlfikc6YI33lm2qu4mw==", + "type": "package", + "path": "diacritics/4.1.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "README.md", + "diacritics.4.1.4.nupkg.sha512", + "diacritics.nuspec", + "lib/net48/Diacritics.dll", + "lib/net48/Diacritics.xml", + "lib/net7.0/Diacritics.dll", + "lib/net7.0/Diacritics.xml", + "lib/net8.0/Diacritics.dll", + "lib/net8.0/Diacritics.xml", + "lib/net9.0/Diacritics.dll", + "lib/net9.0/Diacritics.xml", + "lib/netstandard1.2/Diacritics.dll", + "lib/netstandard1.2/Diacritics.xml", + "lib/netstandard2.0/Diacritics.dll", + "lib/netstandard2.0/Diacritics.xml", + "lib/netstandard2.1/Diacritics.dll", + "lib/netstandard2.1/Diacritics.xml", + "logo.png" + ] + }, + "ICU4N/60.1.0-alpha.356": { + "sha512": "YMZtDnjcqWzziOKiE7w6Ma7Rl5vuFDxzOsUlHh1QyfghbNEIZQOLRs9MMfwCWAjX6n9UitrF6vLXy55Z5q+4Fg==", + "type": "package", + "path": "icu4n/60.1.0-alpha.356", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "icu4n.60.1.0-alpha.356.nupkg.sha512", + "icu4n.nuspec", + "lib/net40/ICU4N.dll", + "lib/net40/ICU4N.xml", + "lib/net451/ICU4N.dll", + "lib/net451/ICU4N.xml", + "lib/netstandard2.0/ICU4N.dll", + "lib/netstandard2.0/ICU4N.xml", + "unicode-icon-128x128.jpg" + ] + }, + "ICU4N.Transliterator/60.1.0-alpha.356": { + "sha512": "lFOSO6bbEtB6HkWMNDJAq+rFwVyi9g6xVc5O/2xHa6iZnV7wLVDqCbaQ4W4vIeBSQZAafqhxciaEkmAvSdzlCg==", + "type": "package", + "path": "icu4n.transliterator/60.1.0-alpha.356", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "icu4n.transliterator.60.1.0-alpha.356.nupkg.sha512", + "icu4n.transliterator.nuspec", + "lib/net40/ICU4N.Transliterator.dll", + "lib/net40/ICU4N.Transliterator.xml", + "lib/net451/ICU4N.Transliterator.dll", + "lib/net451/ICU4N.Transliterator.xml", + "lib/netstandard2.0/ICU4N.Transliterator.dll", + "lib/netstandard2.0/ICU4N.Transliterator.xml", + "unicode-icon-128x128.jpg" + ] + }, + "IDisposableAnalyzers/4.0.8": { + "sha512": "vNi4NMG0CcJyjXxiNDcQ21FwV/whM9o9OEZKD+oP7tuxAqFEzX/x5OhC3OZJqW/w+8GOtCmJPBquYgMWgz0rfQ==", + "type": "package", + "path": "idisposableanalyzers/4.0.8", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/IDisposableAnalyzers.dll", + "idisposableanalyzers.4.0.8.nupkg.sha512", + "idisposableanalyzers.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "J2N/2.0.0": { + "sha512": "M5bwDajAARZiyqupU+rHQJnsVLxNBOHJ8vKYHd8LcLIb1FgLfzzcJvc31Qo5Xz/GEHFjDF9ScjKL/ks/zRTXuA==", + "type": "package", + "path": "j2n/2.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "j2n-icon-100x100.png", + "j2n.2.0.0.nupkg.sha512", + "j2n.nuspec", + "lib/net40/J2N.dll", + "lib/net40/J2N.xml", + "lib/net45/J2N.dll", + "lib/net45/J2N.xml", + "lib/net5.0/J2N.dll", + "lib/net5.0/J2N.xml", + "lib/net6.0/J2N.dll", + "lib/net6.0/J2N.xml", + "lib/netcoreapp3.0/J2N.dll", + "lib/netcoreapp3.0/J2N.xml", + "lib/netstandard2.0/J2N.dll", + "lib/netstandard2.0/J2N.xml", + "lib/netstandard2.1/J2N.dll", + "lib/netstandard2.1/J2N.xml", + "readme.md" + ] + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers/4.14.0": { + "sha512": "gSWJlDWwmDhtbrEJGiHqvEjz9KthIiFD0qYB8zZ6a7z+xpMSPEtM9yTYELSa58iFWYlzSRqP9FXO6KoT3+ZMtg==", + "type": "package", + "path": "microsoft.codeanalysis.bannedapianalyzers/4.14.0", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.txt", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.dll", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.CSharp.BannedApiAnalyzers.dll", + "analyzers/dotnet/cs/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/de/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/es/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/fr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/it/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ja/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ko/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/pl/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/pt-BR/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ru/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/tr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hans/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hant/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.BannedApiAnalyzers.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.VisualBasic.BannedApiAnalyzers.dll", + "analyzers/dotnet/vb/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/de/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/es/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/fr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/it/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ja/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ko/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/pl/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/pt-BR/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ru/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/tr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hans/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hant/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props", + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets", + "documentation/Microsoft.CodeAnalysis.BannedApiAnalyzers.md", + "documentation/Microsoft.CodeAnalysis.BannedApiAnalyzers.sarif", + "editorconfig/AllRulesDefault/.editorconfig", + "editorconfig/AllRulesDisabled/.editorconfig", + "editorconfig/AllRulesEnabled/.editorconfig", + "editorconfig/ApiDesignRulesDefault/.editorconfig", + "editorconfig/ApiDesignRulesEnabled/.editorconfig", + "editorconfig/DataflowRulesDefault/.editorconfig", + "editorconfig/DataflowRulesEnabled/.editorconfig", + "editorconfig/PortedFromFxCopRulesDefault/.editorconfig", + "editorconfig/PortedFromFxCopRulesEnabled/.editorconfig", + "microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512", + "microsoft.codeanalysis.bannedapianalyzers.nuspec", + "rulesets/AllRulesDefault.ruleset", + "rulesets/AllRulesDisabled.ruleset", + "rulesets/AllRulesEnabled.ruleset", + "rulesets/ApiDesignRulesDefault.ruleset", + "rulesets/ApiDesignRulesEnabled.ruleset", + "rulesets/DataflowRulesDefault.ruleset", + "rulesets/DataflowRulesEnabled.ruleset", + "rulesets/PortedFromFxCopRulesDefault.ruleset", + "rulesets/PortedFromFxCopRulesEnabled.ruleset", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Microsoft.Data.Sqlite.Core/10.0.3": { + "sha512": "onD94qHlvteS2S9eg1T7Huehm3x92no4nU1AyDWjSmT6jDBhY8QF0UapwNhFA+1dArzxM10fV7s6uI1EIK2+dw==", + "type": "package", + "path": "microsoft.data.sqlite.core/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net8.0/Microsoft.Data.Sqlite.dll", + "lib/net8.0/Microsoft.Data.Sqlite.xml", + "lib/netstandard2.0/Microsoft.Data.Sqlite.dll", + "lib/netstandard2.0/Microsoft.Data.Sqlite.xml", + "microsoft.data.sqlite.core.10.0.3.nupkg.sha512", + "microsoft.data.sqlite.core.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore/10.0.3": { + "sha512": "CDEImwD4A7BseABJMCpLZnhfFjmPY/bHwhhS70elc6gLI/bYUEOhxWt7PmaNGYGhIEzOnStlCy5QcVb+8dod5Q==", + "type": "package", + "path": "microsoft.entityframeworkcore/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props", + "lib/net10.0/Microsoft.EntityFrameworkCore.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.xml", + "microsoft.entityframeworkcore.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Abstractions/10.0.3": { + "sha512": "c7Uoz381xnMHNBRB8eHRhGgzUtXbgddlbODhwZRrTSzZWDharp3RkJsFwhxyESbeXhCqmML7VdvjMQ7uu+HreA==", + "type": "package", + "path": "microsoft.entityframeworkcore.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.xml", + "microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.abstractions.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Analyzers/10.0.3": { + "sha512": "lVABgJTyTUNE7Bi0bSu4dWHiCHIXEGzTh/kLh0N07IgU/tIKwTeBPp8tgV4x2iKj4h7iPLo8oXzyHmLDGtAE1g==", + "type": "package", + "path": "microsoft.entityframeworkcore.analyzers/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "analyzers/dotnet/cs/Microsoft.EntityFrameworkCore.Analyzers.dll", + "docs/PACKAGE.md", + "microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.analyzers.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Relational/10.0.3": { + "sha512": "Pmh60OK9neVr/M0FJwm9hlzm2bD4Kd65SID8E6SP5c90tExNgXwORrlEWl0oGU/ig9ifpNN4PSpIrnHNozlT5w==", + "type": "package", + "path": "microsoft.entityframeworkcore.relational/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.xml", + "microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.relational.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Sqlite/10.0.3": { + "sha512": "pXP9l00ZzsZuS6ajUJFLHi5vt3vVxHaxQXCwRs73tyqNbjPX/4ac114wyfAepMSmUKq0roFFoTZ7h+1K1+iQjQ==", + "type": "package", + "path": "microsoft.entityframeworkcore.sqlite/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/_._", + "microsoft.entityframeworkcore.sqlite.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.sqlite.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Sqlite.Core/10.0.3": { + "sha512": "bUkOkSwPqvhNlsMMg5dA/PR9S4cVehiHEIZklakRH6JZCFsEBNvUz8kBRGK8Hi6mcaPKUAmVZQkN3moZsBYZLA==", + "type": "package", + "path": "microsoft.entityframeworkcore.sqlite.core/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Sqlite.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Sqlite.xml", + "microsoft.entityframeworkcore.sqlite.core.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.sqlite.core.nuspec" + ] + }, + "Microsoft.Extensions.Caching.Abstractions/10.0.3": { + "sha512": "5dtXBvI8t3z8pF4tB38JYgi/enCL/DwSXxpqShgFz3SHJ7IzqFIMs6Gu5ik8sNZzcO9qQs3xIDpB3vDamkYG+Q==", + "type": "package", + "path": "microsoft.extensions.caching.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Caching.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Caching.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.xml", + "microsoft.extensions.caching.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.caching.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Caching.Memory/10.0.3": { + "sha512": "No4fVh0z30SWqiWFRoA4PNdrEco6OjXvCqRFvlmRgDQqqks2bRDdeavUgWEiAX153ZAwW9loUgbxcvuP4NKQLg==", + "type": "package", + "path": "microsoft.extensions.caching.memory/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Caching.Memory.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Caching.Memory.targets", + "lib/net10.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net10.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/net462/Microsoft.Extensions.Caching.Memory.dll", + "lib/net462/Microsoft.Extensions.Caching.Memory.xml", + "lib/net8.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net8.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/net9.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net9.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.xml", + "microsoft.extensions.caching.memory.10.0.3.nupkg.sha512", + "microsoft.extensions.caching.memory.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Configuration/10.0.3": { + "sha512": "H1Cjv2xmm7O3iAGmFTcnSM0ZhLQ/7SqefmAvSJoT1PbXoxeYc2fo0mCLn2JlVbr9E6YpoU9q/o0fI9neDJB0xQ==", + "type": "package", + "path": "microsoft.extensions.configuration/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Configuration.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Configuration.targets", + "lib/net10.0/Microsoft.Extensions.Configuration.dll", + "lib/net10.0/Microsoft.Extensions.Configuration.xml", + "lib/net462/Microsoft.Extensions.Configuration.dll", + "lib/net462/Microsoft.Extensions.Configuration.xml", + "lib/net8.0/Microsoft.Extensions.Configuration.dll", + "lib/net8.0/Microsoft.Extensions.Configuration.xml", + "lib/net9.0/Microsoft.Extensions.Configuration.dll", + "lib/net9.0/Microsoft.Extensions.Configuration.xml", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.dll", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.xml", + "microsoft.extensions.configuration.10.0.3.nupkg.sha512", + "microsoft.extensions.configuration.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Configuration.Abstractions/10.0.3": { + "sha512": "xVDHL0+SIgemfh95fTO9cGLe17TWv/ZP0n7m01z8X6pzt2DmQpucioWR/mYZA1sRlkWnkXzfl0JweLNWmE9WMg==", + "type": "package", + "path": "microsoft.extensions.configuration.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Configuration.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Configuration.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "microsoft.extensions.configuration.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.configuration.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Configuration.Binder/10.0.3": { + "sha512": "759UhpKaR5Jsll9kXpkft4z/7tpeF7Dw2rTY/9f9JchaSQTpRFNIPkZFZvoo7fFpbjUaqtDlO5aiGpmQrp/EUA==", + "type": "package", + "path": "microsoft.extensions.configuration.binder/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/cs/Microsoft.Extensions.Configuration.Binder.SourceGeneration.dll", + "analyzers/dotnet/cs/cs/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/de/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/es/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/fr/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/it/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/ja/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/ko/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/pl/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/pt-BR/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/ru/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/tr/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/zh-Hans/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "analyzers/dotnet/cs/zh-Hant/Microsoft.Extensions.Configuration.Binder.SourceGeneration.resources.dll", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets", + "lib/net10.0/Microsoft.Extensions.Configuration.Binder.dll", + "lib/net10.0/Microsoft.Extensions.Configuration.Binder.xml", + "lib/net462/Microsoft.Extensions.Configuration.Binder.dll", + "lib/net462/Microsoft.Extensions.Configuration.Binder.xml", + "lib/net8.0/Microsoft.Extensions.Configuration.Binder.dll", + "lib/net8.0/Microsoft.Extensions.Configuration.Binder.xml", + "lib/net9.0/Microsoft.Extensions.Configuration.Binder.dll", + "lib/net9.0/Microsoft.Extensions.Configuration.Binder.xml", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Binder.dll", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Binder.xml", + "microsoft.extensions.configuration.binder.10.0.3.nupkg.sha512", + "microsoft.extensions.configuration.binder.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyInjection/10.0.3": { + "sha512": "2DLOmC0EkB2smVK8lPP1PIKEgL1arE3CMp9XSIQB/Y7ev5nnnyuM/PizKJ6QfLD08QCYoopSC9SFdbYglDomYg==", + "type": "package", + "path": "microsoft.extensions.dependencyinjection/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyInjection.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.targets", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/net462/Microsoft.Extensions.DependencyInjection.dll", + "lib/net462/Microsoft.Extensions.DependencyInjection.xml", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.dll", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.xml", + "microsoft.extensions.dependencyinjection.10.0.3.nupkg.sha512", + "microsoft.extensions.dependencyinjection.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/10.0.3": { + "sha512": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==", + "type": "package", + "path": "microsoft.extensions.dependencyinjection.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyInjection.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "microsoft.extensions.dependencyinjection.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.dependencyinjection.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyModel/10.0.3": { + "sha512": "31kRjr1fgdJO1UZ/AsjL2noqwht+juHMQ8c/oh8CEsDhlM2+0zwVZVsZjxSfOFiPtn5+6kRGuvSbLAufAPT0kA==", + "type": "package", + "path": "microsoft.extensions.dependencymodel/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyModel.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyModel.targets", + "lib/net10.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net10.0/Microsoft.Extensions.DependencyModel.xml", + "lib/net462/Microsoft.Extensions.DependencyModel.dll", + "lib/net462/Microsoft.Extensions.DependencyModel.xml", + "lib/net8.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net8.0/Microsoft.Extensions.DependencyModel.xml", + "lib/net9.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net9.0/Microsoft.Extensions.DependencyModel.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyModel.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyModel.xml", + "microsoft.extensions.dependencymodel.10.0.3.nupkg.sha512", + "microsoft.extensions.dependencymodel.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Logging/10.0.3": { + "sha512": "8D9Er1cGXNjNDIB+VLBNHn386L5ls2FoiG9a6o12gyn+GG3w6jdfUhzT8dtBnKcevE7/fsVA8MS3FBgFfClFtQ==", + "type": "package", + "path": "microsoft.extensions.logging/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Logging.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Logging.targets", + "lib/net10.0/Microsoft.Extensions.Logging.dll", + "lib/net10.0/Microsoft.Extensions.Logging.xml", + "lib/net462/Microsoft.Extensions.Logging.dll", + "lib/net462/Microsoft.Extensions.Logging.xml", + "lib/net8.0/Microsoft.Extensions.Logging.dll", + "lib/net8.0/Microsoft.Extensions.Logging.xml", + "lib/net9.0/Microsoft.Extensions.Logging.dll", + "lib/net9.0/Microsoft.Extensions.Logging.xml", + "lib/netstandard2.0/Microsoft.Extensions.Logging.dll", + "lib/netstandard2.0/Microsoft.Extensions.Logging.xml", + "lib/netstandard2.1/Microsoft.Extensions.Logging.dll", + "lib/netstandard2.1/Microsoft.Extensions.Logging.xml", + "microsoft.extensions.logging.10.0.3.nupkg.sha512", + "microsoft.extensions.logging.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Logging.Abstractions/10.0.3": { + "sha512": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", + "type": "package", + "path": "microsoft.extensions.logging.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn3.11/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn3.11/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn4.0/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "buildTransitive/net461/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/net462/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.targets", + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net10.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net462/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.xml", + "microsoft.extensions.logging.abstractions.10.0.3.nupkg.sha512", + "microsoft.extensions.logging.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Options/10.0.3": { + "sha512": "hU6WzGTPvPoLA2ng1ILvWQb3g0qORdlHNsxI8IcPLumJb3suimYUl+bbDzdo1V4KFsvVhnMWzysHpKbZaoDQPQ==", + "type": "package", + "path": "microsoft.extensions.options/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Options.SourceGeneration.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "buildTransitive/net461/Microsoft.Extensions.Options.targets", + "buildTransitive/net462/Microsoft.Extensions.Options.targets", + "buildTransitive/net8.0/Microsoft.Extensions.Options.targets", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Options.targets", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Options.targets", + "lib/net10.0/Microsoft.Extensions.Options.dll", + "lib/net10.0/Microsoft.Extensions.Options.xml", + "lib/net462/Microsoft.Extensions.Options.dll", + "lib/net462/Microsoft.Extensions.Options.xml", + "lib/net8.0/Microsoft.Extensions.Options.dll", + "lib/net8.0/Microsoft.Extensions.Options.xml", + "lib/net9.0/Microsoft.Extensions.Options.dll", + "lib/net9.0/Microsoft.Extensions.Options.xml", + "lib/netstandard2.0/Microsoft.Extensions.Options.dll", + "lib/netstandard2.0/Microsoft.Extensions.Options.xml", + "lib/netstandard2.1/Microsoft.Extensions.Options.dll", + "lib/netstandard2.1/Microsoft.Extensions.Options.xml", + "microsoft.extensions.options.10.0.3.nupkg.sha512", + "microsoft.extensions.options.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Primitives/10.0.3": { + "sha512": "GEcpTwo7sUoLGGNTqV1FZEuL+tTD9m81NX/mh099dqGNna07/UGZShKQNZRw4hv6nlliSUwYQgSYc7OR99Jufg==", + "type": "package", + "path": "microsoft.extensions.primitives/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Primitives.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Primitives.targets", + "lib/net10.0/Microsoft.Extensions.Primitives.dll", + "lib/net10.0/Microsoft.Extensions.Primitives.xml", + "lib/net462/Microsoft.Extensions.Primitives.dll", + "lib/net462/Microsoft.Extensions.Primitives.xml", + "lib/net8.0/Microsoft.Extensions.Primitives.dll", + "lib/net8.0/Microsoft.Extensions.Primitives.xml", + "lib/net9.0/Microsoft.Extensions.Primitives.dll", + "lib/net9.0/Microsoft.Extensions.Primitives.xml", + "lib/netstandard2.0/Microsoft.Extensions.Primitives.dll", + "lib/netstandard2.0/Microsoft.Extensions.Primitives.xml", + "microsoft.extensions.primitives.10.0.3.nupkg.sha512", + "microsoft.extensions.primitives.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "NEbml/1.1.0.5": { + "sha512": "svtqDc+hue9kbnqNN2KkK4om/hDrc7K127cNb5FIYfgKgzo+JNDPXNLp8NioCchHhBO3lxWd4Cp/iiZZ3aoUqg==", + "type": "package", + "path": "nebml/1.1.0.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net461/NEbml.Core.dll", + "lib/net461/NEbml.Core.xml", + "lib/netstandard2.0/NEbml.Core.dll", + "lib/netstandard2.0/NEbml.Core.xml", + "nebml.1.1.0.5.nupkg.sha512", + "nebml.nuspec" + ] + }, + "Polly/8.6.5": { + "sha512": "VqtW2ZE/ALvQMAH1cQY3qZ2cF2OXa3oe/HKMdOv6Q02HCoEW0rsFNfcBONXlHBe1TnjWW1vdRxBEkPeq0/2FHA==", + "type": "package", + "path": "polly/8.6.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "lib/net462/Polly.dll", + "lib/net462/Polly.pdb", + "lib/net462/Polly.xml", + "lib/net472/Polly.dll", + "lib/net472/Polly.pdb", + "lib/net472/Polly.xml", + "lib/net6.0/Polly.dll", + "lib/net6.0/Polly.pdb", + "lib/net6.0/Polly.xml", + "lib/netstandard2.0/Polly.dll", + "lib/netstandard2.0/Polly.pdb", + "lib/netstandard2.0/Polly.xml", + "package-icon.png", + "package-readme.md", + "polly.8.6.5.nupkg.sha512", + "polly.nuspec" + ] + }, + "Polly.Core/8.6.5": { + "sha512": "t+sUVrIwvo7UmsgHGgOG9F0GDZSRIm47u2ylH17Gvcv1q5hNEwgD5GoBlFyc0kh/pebmPyrAgvGsR/65ZBaXlg==", + "type": "package", + "path": "polly.core/8.6.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "lib/net462/Polly.Core.dll", + "lib/net462/Polly.Core.pdb", + "lib/net462/Polly.Core.xml", + "lib/net472/Polly.Core.dll", + "lib/net472/Polly.Core.pdb", + "lib/net472/Polly.Core.xml", + "lib/net6.0/Polly.Core.dll", + "lib/net6.0/Polly.Core.pdb", + "lib/net6.0/Polly.Core.xml", + "lib/net8.0/Polly.Core.dll", + "lib/net8.0/Polly.Core.pdb", + "lib/net8.0/Polly.Core.xml", + "lib/netstandard2.0/Polly.Core.dll", + "lib/netstandard2.0/Polly.Core.pdb", + "lib/netstandard2.0/Polly.Core.xml", + "package-icon.png", + "package-readme.md", + "polly.core.8.6.5.nupkg.sha512", + "polly.core.nuspec" + ] + }, + "SerilogAnalyzer/0.15.0": { + "sha512": "sVpwfls4MfNnwIXLSGCgaUnV+c9kgJ8ia6GsyRcpd4Vs3gLogSDtSYBYrre2K2u/PNMo8GgG09RehwVnze70Tw==", + "type": "package", + "path": "seriloganalyzer/0.15.0", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/SerilogAnalyzer.dll", + "seriloganalyzer.0.15.0.nupkg.sha512", + "seriloganalyzer.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "SmartAnalyzers.MultithreadingAnalyzer/1.1.31": { + "sha512": "2f2k7bbhDd132ArglCKpzKoWBcp3uzbIFcb4aosnlqIKlfYKDE2HevBVRNVa+LkWFnjXFFWs47Bo96fu8iS//Q==", + "type": "package", + "path": "smartanalyzers.multithreadinganalyzer/1.1.31", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/SmartAnalyzers.MultithreadingAnalyzer.dll", + "smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512", + "smartanalyzers.multithreadinganalyzer.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "SQLitePCLRaw.bundle_e_sqlite3/2.1.11": { + "sha512": "DC4nA7yWnf4UZdgJDF+9Mus4/cb0Y3Sfgi3gDnAoKNAIBwzkskNAbNbyu+u4atT0ruVlZNJfwZmwiEwE5oz9LQ==", + "type": "package", + "path": "sqlitepclraw.bundle_e_sqlite3/2.1.11", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/monoandroid90/SQLitePCLRaw.batteries_v2.dll", + "lib/net461/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-android31.0/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-android31.0/SQLitePCLRaw.batteries_v2.xml", + "lib/net6.0-ios14.0/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-ios14.2/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-tvos10.0/SQLitePCLRaw.batteries_v2.dll", + "lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll", + "lib/xamarinios10/SQLitePCLRaw.batteries_v2.dll", + "sqlitepclraw.bundle_e_sqlite3.2.1.11.nupkg.sha512", + "sqlitepclraw.bundle_e_sqlite3.nuspec" + ] + }, + "SQLitePCLRaw.core/2.1.11": { + "sha512": "PK0GLFkfhZzLQeR3PJf71FmhtHox+U3vcY6ZtswoMjrefkB9k6ErNJEnwXqc5KgXDSjige2XXrezqS39gkpQKA==", + "type": "package", + "path": "sqlitepclraw.core/2.1.11", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/SQLitePCLRaw.core.dll", + "sqlitepclraw.core.2.1.11.nupkg.sha512", + "sqlitepclraw.core.nuspec" + ] + }, + "SQLitePCLRaw.lib.e_sqlite3/2.1.11": { + "sha512": "Ev2ytaXiOlWZ4b3R67GZBsemTINslLD1DCJr2xiacpn4tbapu0Q4dHEzSvZSMnVWeE5nlObU3VZN2p81q3XOYQ==", + "type": "package", + "path": "sqlitepclraw.lib.e_sqlite3/2.1.11", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "buildTransitive/net461/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net6.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net7.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net8.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net9.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "lib/net461/_._", + "lib/netstandard2.0/_._", + "runtimes/browser-wasm/nativeassets/net6.0/e_sqlite3.a", + "runtimes/browser-wasm/nativeassets/net7.0/e_sqlite3.a", + "runtimes/browser-wasm/nativeassets/net8.0/e_sqlite3.a", + "runtimes/browser-wasm/nativeassets/net9.0/e_sqlite3.a", + "runtimes/linux-arm/native/libe_sqlite3.so", + "runtimes/linux-arm64/native/libe_sqlite3.so", + "runtimes/linux-armel/native/libe_sqlite3.so", + "runtimes/linux-mips64/native/libe_sqlite3.so", + "runtimes/linux-musl-arm/native/libe_sqlite3.so", + "runtimes/linux-musl-arm64/native/libe_sqlite3.so", + "runtimes/linux-musl-riscv64/native/libe_sqlite3.so", + "runtimes/linux-musl-s390x/native/libe_sqlite3.so", + "runtimes/linux-musl-x64/native/libe_sqlite3.so", + "runtimes/linux-ppc64le/native/libe_sqlite3.so", + "runtimes/linux-riscv64/native/libe_sqlite3.so", + "runtimes/linux-s390x/native/libe_sqlite3.so", + "runtimes/linux-x64/native/libe_sqlite3.so", + "runtimes/linux-x86/native/libe_sqlite3.so", + "runtimes/maccatalyst-arm64/native/libe_sqlite3.dylib", + "runtimes/maccatalyst-x64/native/libe_sqlite3.dylib", + "runtimes/osx-arm64/native/libe_sqlite3.dylib", + "runtimes/osx-x64/native/libe_sqlite3.dylib", + "runtimes/win-arm/native/e_sqlite3.dll", + "runtimes/win-arm64/native/e_sqlite3.dll", + "runtimes/win-x64/native/e_sqlite3.dll", + "runtimes/win-x86/native/e_sqlite3.dll", + "runtimes/win10-arm/nativeassets/uap10.0/e_sqlite3.dll", + "runtimes/win10-arm64/nativeassets/uap10.0/e_sqlite3.dll", + "runtimes/win10-x64/nativeassets/uap10.0/e_sqlite3.dll", + "runtimes/win10-x86/nativeassets/uap10.0/e_sqlite3.dll", + "sqlitepclraw.lib.e_sqlite3.2.1.11.nupkg.sha512", + "sqlitepclraw.lib.e_sqlite3.nuspec" + ] + }, + "SQLitePCLRaw.provider.e_sqlite3/2.1.11": { + "sha512": "Y/0ZkR+r0Cg3DQFuCl1RBnv/tmxpIZRU3HUvelPw6MVaKHwYYR8YNvgs0vuNuXCMvlyJ+Fh88U1D4tah1tt6qw==", + "type": "package", + "path": "sqlitepclraw.provider.e_sqlite3/2.1.11", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net6.0-windows7.0/SQLitePCLRaw.provider.e_sqlite3.dll", + "lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll", + "lib/netstandard2.0/SQLitePCLRaw.provider.e_sqlite3.dll", + "sqlitepclraw.provider.e_sqlite3.2.1.11.nupkg.sha512", + "sqlitepclraw.provider.e_sqlite3.nuspec" + ] + }, + "StyleCop.Analyzers/1.2.0-beta.556": { + "sha512": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", + "type": "package", + "path": "stylecop.analyzers/1.2.0-beta.556", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "THIRD-PARTY-NOTICES.txt", + "stylecop.analyzers.1.2.0-beta.556.nupkg.sha512", + "stylecop.analyzers.nuspec" + ] + }, + "StyleCop.Analyzers.Unstable/1.2.0.556": { + "sha512": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==", + "type": "package", + "path": "stylecop.analyzers.unstable/1.2.0.556", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "THIRD-PARTY-NOTICES.txt", + "analyzers/dotnet/cs/StyleCop.Analyzers.CodeFixes.dll", + "analyzers/dotnet/cs/StyleCop.Analyzers.dll", + "analyzers/dotnet/cs/de-DE/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/en-GB/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/es-MX/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/fr-FR/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/pl-PL/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/pt-BR/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/ru-RU/StyleCop.Analyzers.resources.dll", + "rulesets/StyleCopAnalyzersDefault.ruleset", + "stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512", + "stylecop.analyzers.unstable.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Jellyfin.CodeAnalysis/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "msbuildProject": "../src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "Jellyfin.Common/10.12.0": { + "type": "project", + "path": "../MediaBrowser.Common/MediaBrowser.Common.csproj", + "msbuildProject": "../MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "Jellyfin.Controller/10.12.0": { + "type": "project", + "path": "../MediaBrowser.Controller/MediaBrowser.Controller.csproj", + "msbuildProject": "../MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "Jellyfin.Data/10.12.0": { + "type": "project", + "path": "../Jellyfin.Data/Jellyfin.Data.csproj", + "msbuildProject": "../Jellyfin.Data/Jellyfin.Data.csproj" + }, + "Jellyfin.Database.Implementations/10.11.0": { + "type": "project", + "path": "../src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "msbuildProject": "../src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + }, + "Jellyfin.Database.Providers.Sqlite/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj", + "msbuildProject": "../src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj" + }, + "Jellyfin.Extensions/10.12.0": { + "type": "project", + "path": "../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "msbuildProject": "../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" + }, + "Jellyfin.MediaEncoding.Keyframes/10.11.0": { + "type": "project", + "path": "../src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj", + "msbuildProject": "../src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj" + }, + "Jellyfin.Model/10.12.0": { + "type": "project", + "path": "../MediaBrowser.Model/MediaBrowser.Model.csproj", + "msbuildProject": "../MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "Jellyfin.Naming/10.12.0": { + "type": "project", + "path": "../Emby.Naming/Emby.Naming.csproj", + "msbuildProject": "../Emby.Naming/Emby.Naming.csproj" + } + }, + "projectFileDependencyGroups": { + "net10.0": [ + "AsyncKeyedLock >= 8.0.2", + "IDisposableAnalyzers >= 4.0.8", + "Jellyfin.CodeAnalysis >= 1.0.0", + "Jellyfin.Controller >= 10.12.0", + "Jellyfin.Data >= 10.12.0", + "Jellyfin.Database.Implementations >= 10.11.0", + "Jellyfin.Database.Providers.Sqlite >= 1.0.0", + "Jellyfin.Model >= 10.12.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers >= 4.14.0", + "Microsoft.EntityFrameworkCore.Relational >= 10.0.3", + "SerilogAnalyzer >= 0.15.0", + "SmartAnalyzers.MultithreadingAnalyzer >= 1.1.31", + "StyleCop.Analyzers >= 1.2.0-beta.556" + ] + }, + "packageFolders": { + "/home/wjones/.nuget/packages/": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj", + "projectName": "Jellyfin.Server.Implementations", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "AsyncKeyedLock": { + "target": "Package", + "version": "[8.0.2, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Relational": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + } +} \ No newline at end of file diff --git a/Jellyfin.Server.Implementations/obj/project.nuget.cache b/Jellyfin.Server.Implementations/obj/project.nuget.cache new file mode 100644 index 00000000..1d86a5b3 --- /dev/null +++ b/Jellyfin.Server.Implementations/obj/project.nuget.cache @@ -0,0 +1,47 @@ +{ + "version": 2, + "dgSpecHash": "tuMKldGZn8g=", + "success": true, + "projectFilePath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj", + "expectedPackageFiles": [ + "/home/wjones/.nuget/packages/asynckeyedlock/8.0.2/asynckeyedlock.8.0.2.nupkg.sha512", + "/home/wjones/.nuget/packages/bitfaster.caching/2.5.4/bitfaster.caching.2.5.4.nupkg.sha512", + "/home/wjones/.nuget/packages/diacritics/4.1.4/diacritics.4.1.4.nupkg.sha512", + "/home/wjones/.nuget/packages/icu4n/60.1.0-alpha.356/icu4n.60.1.0-alpha.356.nupkg.sha512", + "/home/wjones/.nuget/packages/icu4n.transliterator/60.1.0-alpha.356/icu4n.transliterator.60.1.0-alpha.356.nupkg.sha512", + "/home/wjones/.nuget/packages/idisposableanalyzers/4.0.8/idisposableanalyzers.4.0.8.nupkg.sha512", + "/home/wjones/.nuget/packages/j2n/2.0.0/j2n.2.0.0.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0/microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.data.sqlite.core/10.0.3/microsoft.data.sqlite.core.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore/10.0.3/microsoft.entityframeworkcore.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.abstractions/10.0.3/microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.analyzers/10.0.3/microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.relational/10.0.3/microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.sqlite/10.0.3/microsoft.entityframeworkcore.sqlite.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.sqlite.core/10.0.3/microsoft.entityframeworkcore.sqlite.core.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.caching.abstractions/10.0.3/microsoft.extensions.caching.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.caching.memory/10.0.3/microsoft.extensions.caching.memory.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.configuration/10.0.3/microsoft.extensions.configuration.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.configuration.abstractions/10.0.3/microsoft.extensions.configuration.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.configuration.binder/10.0.3/microsoft.extensions.configuration.binder.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection/10.0.3/microsoft.extensions.dependencyinjection.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/10.0.3/microsoft.extensions.dependencyinjection.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.dependencymodel/10.0.3/microsoft.extensions.dependencymodel.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.logging/10.0.3/microsoft.extensions.logging.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.logging.abstractions/10.0.3/microsoft.extensions.logging.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.options/10.0.3/microsoft.extensions.options.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.primitives/10.0.3/microsoft.extensions.primitives.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/nebml/1.1.0.5/nebml.1.1.0.5.nupkg.sha512", + "/home/wjones/.nuget/packages/polly/8.6.5/polly.8.6.5.nupkg.sha512", + "/home/wjones/.nuget/packages/polly.core/8.6.5/polly.core.8.6.5.nupkg.sha512", + "/home/wjones/.nuget/packages/seriloganalyzer/0.15.0/seriloganalyzer.0.15.0.nupkg.sha512", + "/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31/smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512", + "/home/wjones/.nuget/packages/sqlitepclraw.bundle_e_sqlite3/2.1.11/sqlitepclraw.bundle_e_sqlite3.2.1.11.nupkg.sha512", + "/home/wjones/.nuget/packages/sqlitepclraw.core/2.1.11/sqlitepclraw.core.2.1.11.nupkg.sha512", + "/home/wjones/.nuget/packages/sqlitepclraw.lib.e_sqlite3/2.1.11/sqlitepclraw.lib.e_sqlite3.2.1.11.nupkg.sha512", + "/home/wjones/.nuget/packages/sqlitepclraw.provider.e_sqlite3/2.1.11/sqlitepclraw.provider.e_sqlite3.2.1.11.nupkg.sha512", + "/home/wjones/.nuget/packages/stylecop.analyzers/1.2.0-beta.556/stylecop.analyzers.1.2.0-beta.556.nupkg.sha512", + "/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556/stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Jellyfin.Server.Implementations/obj/project.packagespec.json b/Jellyfin.Server.Implementations/obj/project.packagespec.json new file mode 100644 index 00000000..10e7260b --- /dev/null +++ b/Jellyfin.Server.Implementations/obj/project.packagespec.json @@ -0,0 +1 @@ +"restore":{"projectUniqueName":"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj","projectName":"Jellyfin.Server.Implementations","projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj","outputPath":"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/obj/","projectStyle":"PackageReference","centralPackageVersionsManagementEnabled":true,"originalTargetFrameworks":["net10.0"],"sources":{"/usr/lib/dotnet/library-packs":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net10.0":{"targetAlias":"net10.0","projectReferences":{"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj"}}}},"warningProperties":{"allWarningsAsErrors":true,"warnAsError":["NU1605"],"warnNotAsError":["NU1902","NU1903"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"all"},"SdkAnalysisLevel":"10.0.100"}"frameworks":{"net10.0":{"targetAlias":"net10.0","dependencies":{"AsyncKeyedLock":{"target":"Package","version":"[8.0.2, )","versionCentrallyManaged":true},"IDisposableAnalyzers":{"include":"Runtime, Build, Native, ContentFiles, Analyzers","suppressParent":"All","target":"Package","version":"[4.0.8, )","versionCentrallyManaged":true},"Microsoft.CodeAnalysis.BannedApiAnalyzers":{"include":"Runtime, Build, Native, ContentFiles, Analyzers","suppressParent":"All","target":"Package","version":"[4.14.0, )","versionCentrallyManaged":true},"Microsoft.EntityFrameworkCore.Relational":{"target":"Package","version":"[10.0.3, )","versionCentrallyManaged":true},"SerilogAnalyzer":{"suppressParent":"All","target":"Package","version":"[0.15.0, )","versionCentrallyManaged":true},"SmartAnalyzers.MultithreadingAnalyzer":{"suppressParent":"All","target":"Package","version":"[1.1.31, )","versionCentrallyManaged":true},"StyleCop.Analyzers":{"suppressParent":"All","target":"Package","version":"[1.2.0-beta.556, )","versionCentrallyManaged":true}},"centralPackageVersions":{"AsyncKeyedLock":"8.0.2","AutoFixture":"4.18.1","AutoFixture.AutoMoq":"4.18.1","AutoFixture.Xunit2":"4.18.1","BDInfo":"0.8.0","BitFaster.Caching":"2.5.4","BlurHashSharp":"1.4.0-pre.1","BlurHashSharp.SkiaSharp":"1.4.0-pre.1","CommandLineParser":"2.9.1","coverlet.collector":"8.0.0","Diacritics":"4.1.4","DiscUtils.Udf":"0.16.13","DotNet.Glob":"3.1.3","FsCheck.Xunit":"3.3.2","HarfBuzzSharp.NativeAssets.Linux":"8.3.1.1","ICU4N.Transliterator":"60.1.0-alpha.356","IDisposableAnalyzers":"4.0.8","Ignore":"0.2.1","Jellyfin.XmlTv":"10.8.0","libse":"4.0.12","LrcParser":"2025.623.0","MetaBrainz.MusicBrainz":"8.0.1","Microsoft.AspNetCore.Authorization":"10.0.3","Microsoft.AspNetCore.Mvc.Testing":"10.0.3","Microsoft.CodeAnalysis.Analyzers":"3.11.0","Microsoft.CodeAnalysis.BannedApiAnalyzers":"4.14.0","Microsoft.CodeAnalysis.Common":"5.0.0","Microsoft.CodeAnalysis.CSharp":"5.0.0","Microsoft.Data.Sqlite":"10.0.3","Microsoft.EntityFrameworkCore.Design":"10.0.3","Microsoft.EntityFrameworkCore.Relational":"10.0.3","Microsoft.EntityFrameworkCore.Sqlite":"10.0.3","Microsoft.EntityFrameworkCore.Tools":"10.0.3","Microsoft.Extensions.Caching.Abstractions":"10.0.3","Microsoft.Extensions.Caching.Memory":"10.0.3","Microsoft.Extensions.Configuration.Abstractions":"10.0.3","Microsoft.Extensions.Configuration.Binder":"10.0.3","Microsoft.Extensions.DependencyInjection":"10.0.3","Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore":"10.0.3","Microsoft.Extensions.Hosting.Abstractions":"10.0.3","Microsoft.Extensions.Http":"10.0.3","Microsoft.Extensions.Logging":"10.0.3","Microsoft.Extensions.Options":"10.0.3","Microsoft.NET.Test.Sdk":"18.0.1","MimeTypes":"2.5.2","Moq":"4.18.4","Morestachio":"5.0.1.631","NEbml":"1.1.0.5","Newtonsoft.Json":"13.0.4","PlaylistsNET":"1.4.1","Polly":"8.6.5","prometheus-net":"8.2.1","prometheus-net.AspNetCore":"8.2.1","prometheus-net.DotNetRuntime":"4.4.1","Serilog.AspNetCore":"10.0.0","Serilog.Enrichers.Thread":"4.0.0","Serilog.Expressions":"5.0.0","Serilog.Settings.Configuration":"10.0.0","Serilog.Sinks.Async":"2.1.0","Serilog.Sinks.Console":"6.1.1","Serilog.Sinks.File":"7.0.0","Serilog.Sinks.Graylog":"3.1.1","SerilogAnalyzer":"0.15.0","SharpFuzz":"2.2.0","SkiaSharp":"[3.116.1]","SkiaSharp.HarfBuzz":"[3.116.1]","SkiaSharp.NativeAssets.Linux":"[3.116.1]","SmartAnalyzers.MultithreadingAnalyzer":"1.1.31","StyleCop.Analyzers":"1.2.0-beta.556","Svg.Skia":"3.4.1","Swashbuckle.AspNetCore":"7.3.2","Swashbuckle.AspNetCore.ReDoc":"6.9.0","System.Text.Json":"10.0.3","TagLibSharp":"2.3.0","TMDbLib":"2.3.0","UTF.Unknown":"2.6.0","xunit":"2.9.3","Xunit.Priority":"1.1.6","xunit.runner.visualstudio":"2.8.2","Xunit.SkippableFact":"1.5.61","z440.atl.core":"7.11.0"},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json","packagesToPrune":{"Microsoft.CSharp":"(,4.7.32767]","Microsoft.VisualBasic":"(,10.4.32767]","Microsoft.Win32.Primitives":"(,4.3.32767]","Microsoft.Win32.Registry":"(,5.0.32767]","runtime.any.System.Collections":"(,4.3.32767]","runtime.any.System.Diagnostics.Tools":"(,4.3.32767]","runtime.any.System.Diagnostics.Tracing":"(,4.3.32767]","runtime.any.System.Globalization":"(,4.3.32767]","runtime.any.System.Globalization.Calendars":"(,4.3.32767]","runtime.any.System.IO":"(,4.3.32767]","runtime.any.System.Reflection":"(,4.3.32767]","runtime.any.System.Reflection.Extensions":"(,4.3.32767]","runtime.any.System.Reflection.Primitives":"(,4.3.32767]","runtime.any.System.Resources.ResourceManager":"(,4.3.32767]","runtime.any.System.Runtime":"(,4.3.32767]","runtime.any.System.Runtime.Handles":"(,4.3.32767]","runtime.any.System.Runtime.InteropServices":"(,4.3.32767]","runtime.any.System.Text.Encoding":"(,4.3.32767]","runtime.any.System.Text.Encoding.Extensions":"(,4.3.32767]","runtime.any.System.Threading.Tasks":"(,4.3.32767]","runtime.any.System.Threading.Timer":"(,4.3.32767]","runtime.aot.System.Collections":"(,4.3.32767]","runtime.aot.System.Diagnostics.Tools":"(,4.3.32767]","runtime.aot.System.Diagnostics.Tracing":"(,4.3.32767]","runtime.aot.System.Globalization":"(,4.3.32767]","runtime.aot.System.Globalization.Calendars":"(,4.3.32767]","runtime.aot.System.IO":"(,4.3.32767]","runtime.aot.System.Reflection":"(,4.3.32767]","runtime.aot.System.Reflection.Extensions":"(,4.3.32767]","runtime.aot.System.Reflection.Primitives":"(,4.3.32767]","runtime.aot.System.Resources.ResourceManager":"(,4.3.32767]","runtime.aot.System.Runtime":"(,4.3.32767]","runtime.aot.System.Runtime.Handles":"(,4.3.32767]","runtime.aot.System.Runtime.InteropServices":"(,4.3.32767]","runtime.aot.System.Text.Encoding":"(,4.3.32767]","runtime.aot.System.Text.Encoding.Extensions":"(,4.3.32767]","runtime.aot.System.Threading.Tasks":"(,4.3.32767]","runtime.aot.System.Threading.Timer":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.unix.Microsoft.Win32.Primitives":"(,4.3.32767]","runtime.unix.System.Console":"(,4.3.32767]","runtime.unix.System.Diagnostics.Debug":"(,4.3.32767]","runtime.unix.System.IO.FileSystem":"(,4.3.32767]","runtime.unix.System.Net.Primitives":"(,4.3.32767]","runtime.unix.System.Net.Sockets":"(,4.3.32767]","runtime.unix.System.Private.Uri":"(,4.3.32767]","runtime.unix.System.Runtime.Extensions":"(,4.3.32767]","runtime.win.Microsoft.Win32.Primitives":"(,4.3.32767]","runtime.win.System.Console":"(,4.3.32767]","runtime.win.System.Diagnostics.Debug":"(,4.3.32767]","runtime.win.System.IO.FileSystem":"(,4.3.32767]","runtime.win.System.Net.Primitives":"(,4.3.32767]","runtime.win.System.Net.Sockets":"(,4.3.32767]","runtime.win.System.Runtime.Extensions":"(,4.3.32767]","runtime.win10-arm-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win10-arm64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win10-x64-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win10-x86-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win7-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win7-x86.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win7.System.Private.Uri":"(,4.3.32767]","runtime.win8-arm.runtime.native.System.IO.Compression":"(,4.3.32767]","System.AppContext":"(,4.3.32767]","System.Buffers":"(,5.0.32767]","System.Collections":"(,4.3.32767]","System.Collections.Concurrent":"(,4.3.32767]","System.Collections.Immutable":"(,10.0.32767]","System.Collections.NonGeneric":"(,4.3.32767]","System.Collections.Specialized":"(,4.3.32767]","System.ComponentModel":"(,4.3.32767]","System.ComponentModel.Annotations":"(,4.3.32767]","System.ComponentModel.EventBasedAsync":"(,4.3.32767]","System.ComponentModel.Primitives":"(,4.3.32767]","System.ComponentModel.TypeConverter":"(,4.3.32767]","System.Console":"(,4.3.32767]","System.Data.Common":"(,4.3.32767]","System.Data.DataSetExtensions":"(,4.4.32767]","System.Diagnostics.Contracts":"(,4.3.32767]","System.Diagnostics.Debug":"(,4.3.32767]","System.Diagnostics.DiagnosticSource":"(,10.0.32767]","System.Diagnostics.FileVersionInfo":"(,4.3.32767]","System.Diagnostics.Process":"(,4.3.32767]","System.Diagnostics.StackTrace":"(,4.3.32767]","System.Diagnostics.TextWriterTraceListener":"(,4.3.32767]","System.Diagnostics.Tools":"(,4.3.32767]","System.Diagnostics.TraceSource":"(,4.3.32767]","System.Diagnostics.Tracing":"(,4.3.32767]","System.Drawing.Primitives":"(,4.3.32767]","System.Dynamic.Runtime":"(,4.3.32767]","System.Formats.Asn1":"(,10.0.32767]","System.Formats.Tar":"(,10.0.32767]","System.Globalization":"(,4.3.32767]","System.Globalization.Calendars":"(,4.3.32767]","System.Globalization.Extensions":"(,4.3.32767]","System.IO":"(,4.3.32767]","System.IO.Compression":"(,4.3.32767]","System.IO.Compression.ZipFile":"(,4.3.32767]","System.IO.FileSystem":"(,4.3.32767]","System.IO.FileSystem.AccessControl":"(,4.4.32767]","System.IO.FileSystem.DriveInfo":"(,4.3.32767]","System.IO.FileSystem.Primitives":"(,4.3.32767]","System.IO.FileSystem.Watcher":"(,4.3.32767]","System.IO.IsolatedStorage":"(,4.3.32767]","System.IO.MemoryMappedFiles":"(,4.3.32767]","System.IO.Pipelines":"(,10.0.32767]","System.IO.Pipes":"(,4.3.32767]","System.IO.Pipes.AccessControl":"(,5.0.32767]","System.IO.UnmanagedMemoryStream":"(,4.3.32767]","System.Linq":"(,4.3.32767]","System.Linq.AsyncEnumerable":"(,10.0.32767]","System.Linq.Expressions":"(,4.3.32767]","System.Linq.Parallel":"(,4.3.32767]","System.Linq.Queryable":"(,4.3.32767]","System.Memory":"(,5.0.32767]","System.Net.Http":"(,4.3.32767]","System.Net.Http.Json":"(,10.0.32767]","System.Net.NameResolution":"(,4.3.32767]","System.Net.NetworkInformation":"(,4.3.32767]","System.Net.Ping":"(,4.3.32767]","System.Net.Primitives":"(,4.3.32767]","System.Net.Requests":"(,4.3.32767]","System.Net.Security":"(,4.3.32767]","System.Net.ServerSentEvents":"(,10.0.32767]","System.Net.Sockets":"(,4.3.32767]","System.Net.WebHeaderCollection":"(,4.3.32767]","System.Net.WebSockets":"(,4.3.32767]","System.Net.WebSockets.Client":"(,4.3.32767]","System.Numerics.Vectors":"(,5.0.32767]","System.ObjectModel":"(,4.3.32767]","System.Private.DataContractSerialization":"(,4.3.32767]","System.Private.Uri":"(,4.3.32767]","System.Reflection":"(,4.3.32767]","System.Reflection.DispatchProxy":"(,6.0.32767]","System.Reflection.Emit":"(,4.7.32767]","System.Reflection.Emit.ILGeneration":"(,4.7.32767]","System.Reflection.Emit.Lightweight":"(,4.7.32767]","System.Reflection.Extensions":"(,4.3.32767]","System.Reflection.Metadata":"(,10.0.32767]","System.Reflection.Primitives":"(,4.3.32767]","System.Reflection.TypeExtensions":"(,4.3.32767]","System.Resources.Reader":"(,4.3.32767]","System.Resources.ResourceManager":"(,4.3.32767]","System.Resources.Writer":"(,4.3.32767]","System.Runtime":"(,4.3.32767]","System.Runtime.CompilerServices.Unsafe":"(,7.0.32767]","System.Runtime.CompilerServices.VisualC":"(,4.3.32767]","System.Runtime.Extensions":"(,4.3.32767]","System.Runtime.Handles":"(,4.3.32767]","System.Runtime.InteropServices":"(,4.3.32767]","System.Runtime.InteropServices.RuntimeInformation":"(,4.3.32767]","System.Runtime.Loader":"(,4.3.32767]","System.Runtime.Numerics":"(,4.3.32767]","System.Runtime.Serialization.Formatters":"(,4.3.32767]","System.Runtime.Serialization.Json":"(,4.3.32767]","System.Runtime.Serialization.Primitives":"(,4.3.32767]","System.Runtime.Serialization.Xml":"(,4.3.32767]","System.Security.AccessControl":"(,6.0.32767]","System.Security.Claims":"(,4.3.32767]","System.Security.Cryptography.Algorithms":"(,4.3.32767]","System.Security.Cryptography.Cng":"(,5.0.32767]","System.Security.Cryptography.Csp":"(,4.3.32767]","System.Security.Cryptography.Encoding":"(,4.3.32767]","System.Security.Cryptography.OpenSsl":"(,5.0.32767]","System.Security.Cryptography.Primitives":"(,4.3.32767]","System.Security.Cryptography.X509Certificates":"(,4.3.32767]","System.Security.Principal":"(,4.3.32767]","System.Security.Principal.Windows":"(,5.0.32767]","System.Security.SecureString":"(,4.3.32767]","System.Text.Encoding":"(,4.3.32767]","System.Text.Encoding.CodePages":"(,10.0.32767]","System.Text.Encoding.Extensions":"(,4.3.32767]","System.Text.Encodings.Web":"(,10.0.32767]","System.Text.Json":"(,10.0.32767]","System.Text.RegularExpressions":"(,4.3.32767]","System.Threading":"(,4.3.32767]","System.Threading.AccessControl":"(,10.0.32767]","System.Threading.Channels":"(,10.0.32767]","System.Threading.Overlapped":"(,4.3.32767]","System.Threading.Tasks":"(,4.3.32767]","System.Threading.Tasks.Dataflow":"(,10.0.32767]","System.Threading.Tasks.Extensions":"(,5.0.32767]","System.Threading.Tasks.Parallel":"(,4.3.32767]","System.Threading.Thread":"(,4.3.32767]","System.Threading.ThreadPool":"(,4.3.32767]","System.Threading.Timer":"(,4.3.32767]","System.ValueTuple":"(,4.5.32767]","System.Xml.ReaderWriter":"(,4.3.32767]","System.Xml.XDocument":"(,4.3.32767]","System.Xml.XmlDocument":"(,4.3.32767]","System.Xml.XmlSerializer":"(,4.3.32767]","System.Xml.XPath":"(,4.3.32767]","System.Xml.XPath.XDocument":"(,5.0.32767]"}}} \ No newline at end of file diff --git a/Jellyfin.Server.Implementations/obj/rider.project.model.nuget.info b/Jellyfin.Server.Implementations/obj/rider.project.model.nuget.info new file mode 100644 index 00000000..da87fcd1 --- /dev/null +++ b/Jellyfin.Server.Implementations/obj/rider.project.model.nuget.info @@ -0,0 +1 @@ +17714532049100000 \ No newline at end of file diff --git a/Jellyfin.Server.Implementations/obj/rider.project.restore.info b/Jellyfin.Server.Implementations/obj/rider.project.restore.info new file mode 100644 index 00000000..55ec81d1 --- /dev/null +++ b/Jellyfin.Server.Implementations/obj/rider.project.restore.info @@ -0,0 +1 @@ +17715044199800000 \ No newline at end of file diff --git a/Jellyfin.Server/Configuration/CorsPolicyProvider.cs b/Jellyfin.Server/Configuration/CorsPolicyProvider.cs new file mode 100644 index 00000000..8ef78d92 --- /dev/null +++ b/Jellyfin.Server/Configuration/CorsPolicyProvider.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading.Tasks; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Cors.Infrastructure; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Server.Configuration +{ + /// <summary> + /// Cors policy provider. + /// </summary> + public class CorsPolicyProvider : ICorsPolicyProvider + { + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="CorsPolicyProvider"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + public CorsPolicyProvider(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// <inheritdoc /> + public Task<CorsPolicy?> GetPolicyAsync(HttpContext context, string? policyName) + { + var corsHosts = _serverConfigurationManager.Configuration.CorsHosts; + var builder = new CorsPolicyBuilder() + .AllowAnyMethod() + .AllowAnyHeader(); + + // No hosts configured or only default configured. + if (corsHosts.Length == 0 + || (corsHosts.Length == 1 + && string.Equals(corsHosts[0], CorsConstants.AnyOrigin, StringComparison.Ordinal))) + { + builder.AllowAnyOrigin(); + } + else + { + builder.WithOrigins(corsHosts) + .AllowCredentials(); + } + + return Task.FromResult<CorsPolicy?>(builder.Build()); + } + } +} diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs new file mode 100644 index 00000000..2548ddea --- /dev/null +++ b/Jellyfin.Server/CoreAppHost.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Emby.Server.Implementations; +using Emby.Server.Implementations.Session; +using Jellyfin.Api.WebSocketListeners; +using Jellyfin.Database.Implementations; +using Jellyfin.Drawing; +using Jellyfin.Drawing.Skia; +using Jellyfin.LiveTv; +using Jellyfin.Server.Implementations.Activity; +using Jellyfin.Server.Implementations.Devices; +using Jellyfin.Server.Implementations.Events; +using Jellyfin.Server.Implementations.Extensions; +using Jellyfin.Server.Implementations.Security; +using Jellyfin.Server.Implementations.Trickplay; +using Jellyfin.Server.Implementations.Users; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.BaseItemManager; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Lyrics; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Security; +using MediaBrowser.Controller.Trickplay; +using MediaBrowser.Model.Activity; +using MediaBrowser.Providers.Lyric; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server +{ + /// <summary> + /// Implementation of the abstract <see cref="ApplicationHost" /> class. + /// </summary> + public class CoreAppHost : ApplicationHost + { + /// <summary> + /// Initializes a new instance of the <see cref="CoreAppHost" /> class. + /// </summary> + /// <param name="applicationPaths">The <see cref="ServerApplicationPaths" /> to be used by the <see cref="CoreAppHost" />.</param> + /// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param> + /// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param> + /// <param name="startupConfig">The <see cref="IConfiguration" /> to be used by the <see cref="CoreAppHost" />.</param> + public CoreAppHost( + IServerApplicationPaths applicationPaths, + ILoggerFactory loggerFactory, + IStartupOptions options, + IConfiguration startupConfig) + : base( + applicationPaths, + loggerFactory, + options, + startupConfig) + { + } + + /// <inheritdoc/> + protected override void RegisterServices(IServiceCollection serviceCollection) + { + // Register an image encoder + bool useSkiaEncoder = SkiaEncoder.IsNativeLibAvailable(); + Type imageEncoderType = useSkiaEncoder + ? typeof(SkiaEncoder) + : typeof(NullImageEncoder); + serviceCollection.AddSingleton(typeof(IImageEncoder), imageEncoderType); + + // Log a warning if the Skia encoder could not be used + if (!useSkiaEncoder) + { + Logger.LogWarning("Skia not available. Will fallback to {ImageEncoder}.", nameof(NullImageEncoder)); + } + + serviceCollection.AddEventServices(); + serviceCollection.AddSingleton<IBaseItemManager, BaseItemManager>(); + serviceCollection.AddSingleton<IEventManager, EventManager>(); + + serviceCollection.AddSingleton<IActivityManager, ActivityManager>(); + serviceCollection.AddSingleton<IUserManager, UserManager>(); + serviceCollection.AddSingleton<IAuthenticationProvider, DefaultAuthenticationProvider>(); + serviceCollection.AddSingleton<IAuthenticationProvider, InvalidAuthProvider>(); + serviceCollection.AddSingleton<IPasswordResetProvider, DefaultPasswordResetProvider>(); + serviceCollection.AddSingleton<IDisplayPreferencesManager, DisplayPreferencesManager>(); + serviceCollection.AddSingleton<IDeviceManager, DeviceManager>(); + serviceCollection.AddSingleton<ITrickplayManager, TrickplayManager>(); + + // TODO search the assemblies instead of adding them manually? + serviceCollection.AddSingleton<IWebSocketListener, SessionWebSocketListener>(); + serviceCollection.AddSingleton<IWebSocketListener, ActivityLogWebSocketListener>(); + serviceCollection.AddSingleton<IWebSocketListener, ScheduledTasksWebSocketListener>(); + serviceCollection.AddSingleton<IWebSocketListener, SessionInfoWebSocketListener>(); + + serviceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>(); + + serviceCollection.AddScoped<IAuthenticationManager, AuthenticationManager>(); + + foreach (var type in GetExportTypes<ILyricProvider>()) + { + serviceCollection.AddSingleton(typeof(ILyricProvider), type); + } + + foreach (var type in GetExportTypes<ILyricParser>()) + { + serviceCollection.AddSingleton(typeof(ILyricParser), type); + } + + base.RegisterServices(serviceCollection); + } + + /// <inheritdoc /> + protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal() + { + // Jellyfin.Server + yield return typeof(CoreAppHost).Assembly; + + // Jellyfin.Database.Implementations + yield return typeof(JellyfinDbContext).Assembly; + + // Jellyfin.Server.Implementations + yield return typeof(ServiceCollectionExtensions).Assembly; + + // Jellyfin.LiveTv + yield return typeof(LiveTvManager).Assembly; + } + } +} diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs new file mode 100644 index 00000000..9fd853cf --- /dev/null +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -0,0 +1,121 @@ +using System.Collections.Generic; +using Jellyfin.Api.Middleware; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Builder; +using Microsoft.OpenApi.Models; + +namespace Jellyfin.Server.Extensions +{ + /// <summary> + /// Extensions for adding API specific functionality to the application pipeline. + /// </summary> + public static class ApiApplicationBuilderExtensions + { + /// <summary> + /// Adds swagger and swagger UI to the application pipeline. + /// </summary> + /// <param name="applicationBuilder">The application builder.</param> + /// <param name="serverConfigurationManager">The server configuration.</param> + /// <returns>The updated application builder.</returns> + public static IApplicationBuilder UseJellyfinApiSwagger( + this IApplicationBuilder applicationBuilder, + IServerConfigurationManager serverConfigurationManager) + { + // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), + // specifying the Swagger JSON endpoint. + + var baseUrl = serverConfigurationManager.GetNetworkConfiguration().BaseUrl.Trim('/'); + var apiDocBaseUrl = serverConfigurationManager.GetNetworkConfiguration().BaseUrl; + if (!string.IsNullOrEmpty(baseUrl)) + { + baseUrl += '/'; + } + + return applicationBuilder + .UseSwagger(c => + { + // Custom path requires {documentName}, SwaggerDoc documentName is 'api-docs' + c.RouteTemplate = "{documentName}/openapi.json"; + c.PreSerializeFilters.Add((swagger, httpReq) => + { + swagger.Servers = new List<OpenApiServer> { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}{apiDocBaseUrl}" } }; + }); + }) + .UseSwaggerUI(c => + { + c.DocumentTitle = "Jellyfin API"; + c.SwaggerEndpoint($"/{baseUrl}api-docs/openapi.json", "Jellyfin API"); + c.InjectStylesheet($"/{baseUrl}api-docs/swagger/custom.css"); + c.RoutePrefix = "api-docs/swagger"; + }) + .UseReDoc(c => + { + c.DocumentTitle = "Jellyfin API"; + c.SpecUrl($"/{baseUrl}api-docs/openapi.json"); + c.InjectStylesheet($"/{baseUrl}api-docs/redoc/custom.css"); + c.RoutePrefix = "api-docs/redoc"; + }); + } + + /// <summary> + /// Adds IP based access validation to the application pipeline. + /// </summary> + /// <param name="appBuilder">The application builder.</param> + /// <returns>The updated application builder.</returns> + public static IApplicationBuilder UseIPBasedAccessValidation(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware<IPBasedAccessValidationMiddleware>(); + } + + /// <summary> + /// Enables url decoding before binding to the application pipeline. + /// </summary> + /// <param name="appBuilder">The <see cref="IApplicationBuilder"/>.</param> + /// <returns>The updated application builder.</returns> + public static IApplicationBuilder UseQueryStringDecoding(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware<QueryStringDecodingMiddleware>(); + } + + /// <summary> + /// Adds base url redirection to the application pipeline. + /// </summary> + /// <param name="appBuilder">The application builder.</param> + /// <returns>The updated application builder.</returns> + public static IApplicationBuilder UseBaseUrlRedirection(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware<BaseUrlRedirectionMiddleware>(); + } + + /// <summary> + /// Adds a custom message during server startup to the application pipeline. + /// </summary> + /// <param name="appBuilder">The application builder.</param> + /// <returns>The updated application builder.</returns> + public static IApplicationBuilder UseServerStartupMessage(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware<ServerStartupMessageMiddleware>(); + } + + /// <summary> + /// Adds a WebSocket request handler to the application pipeline. + /// </summary> + /// <param name="appBuilder">The application builder.</param> + /// <returns>The updated application builder.</returns> + public static IApplicationBuilder UseWebSocketHandler(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware<WebSocketHandlerMiddleware>(); + } + + /// <summary> + /// Adds robots.txt redirection to the application pipeline. + /// </summary> + /// <param name="appBuilder">The application builder.</param> + /// <returns>The updated application builder.</returns> + public static IApplicationBuilder UseRobotsRedirection(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware<RobotsRedirectionMiddleware>(); + } + } +} diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs new file mode 100644 index 00000000..9df24fa0 --- /dev/null +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -0,0 +1,362 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Security.Claims; +using Emby.Server.Implementations; +using Jellyfin.Api.Auth; +using Jellyfin.Api.Auth.AnonymousLanAccessPolicy; +using Jellyfin.Api.Auth.DefaultAuthorizationPolicy; +using Jellyfin.Api.Auth.FirstTimeSetupPolicy; +using Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy; +using Jellyfin.Api.Auth.SyncPlayAccessPolicy; +using Jellyfin.Api.Auth.UserPermissionPolicy; +using Jellyfin.Api.Constants; +using Jellyfin.Api.Controllers; +using Jellyfin.Api.Formatters; +using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions.Json; +using Jellyfin.Server.Configuration; +using Jellyfin.Server.Filters; +using MediaBrowser.Common.Api; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Session; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Cors.Infrastructure; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; +using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes; + +namespace Jellyfin.Server.Extensions +{ + /// <summary> + /// API specific extensions for the service collection. + /// </summary> + public static class ApiServiceCollectionExtensions + { + /// <summary> + /// Adds jellyfin API authorization policies to the DI container. + /// </summary> + /// <param name="serviceCollection">The service collection.</param> + /// <returns>The updated service collection.</returns> + public static IServiceCollection AddJellyfinApiAuthorization(this IServiceCollection serviceCollection) + { + // The default handler must be first so that it is evaluated first + serviceCollection.AddSingleton<IAuthorizationHandler, DefaultAuthorizationHandler>(); + serviceCollection.AddSingleton<IAuthorizationHandler, UserPermissionHandler>(); + serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeSetupHandler>(); + serviceCollection.AddSingleton<IAuthorizationHandler, AnonymousLanAccessHandler>(); + serviceCollection.AddSingleton<IAuthorizationHandler, SyncPlayAccessHandler>(); + serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessOrRequiresElevationHandler>(); + + return serviceCollection.AddAuthorizationCore(options => + { + options.DefaultPolicy = new AuthorizationPolicyBuilder() + .AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication) + .AddRequirements(new DefaultAuthorizationRequirement()) + .Build(); + + options.AddPolicy(Policies.AnonymousLanAccessPolicy, new AnonymousLanAccessRequirement()); + options.AddPolicy(Policies.CollectionManagement, new UserPermissionRequirement(PermissionKind.EnableCollectionManagement)); + options.AddPolicy(Policies.Download, new UserPermissionRequirement(PermissionKind.EnableContentDownloading)); + options.AddPolicy(Policies.FirstTimeSetupOrDefault, new FirstTimeSetupRequirement(requireAdmin: false)); + options.AddPolicy(Policies.FirstTimeSetupOrElevated, new FirstTimeSetupRequirement()); + options.AddPolicy(Policies.FirstTimeSetupOrIgnoreParentalControl, new FirstTimeSetupRequirement(false, false)); + options.AddPolicy(Policies.IgnoreParentalControl, new DefaultAuthorizationRequirement(validateParentalSchedule: false)); + options.AddPolicy(Policies.LiveTvAccess, new UserPermissionRequirement(PermissionKind.EnableLiveTvAccess)); + options.AddPolicy(Policies.LiveTvManagement, new UserPermissionRequirement(PermissionKind.EnableLiveTvManagement)); + options.AddPolicy(Policies.LocalAccessOrRequiresElevation, new LocalAccessOrRequiresElevationRequirement()); + options.AddPolicy(Policies.SyncPlayHasAccess, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.HasAccess)); + options.AddPolicy(Policies.SyncPlayCreateGroup, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.CreateGroup)); + options.AddPolicy(Policies.SyncPlayJoinGroup, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.JoinGroup)); + options.AddPolicy(Policies.SyncPlayIsInGroup, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.IsInGroup)); + options.AddPolicy(Policies.SubtitleManagement, new UserPermissionRequirement(PermissionKind.EnableSubtitleManagement)); + options.AddPolicy(Policies.LyricManagement, new UserPermissionRequirement(PermissionKind.EnableLyricManagement)); + options.AddPolicy( + Policies.RequiresElevation, + policy => policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication) + .RequireClaim(ClaimTypes.Role, UserRoles.Administrator)); + }); + } + + /// <summary> + /// Adds custom legacy authentication to the service collection. + /// </summary> + /// <param name="serviceCollection">The service collection.</param> + /// <returns>The updated service collection.</returns> + public static AuthenticationBuilder AddCustomAuthentication(this IServiceCollection serviceCollection) + { + return serviceCollection.AddAuthentication(AuthenticationSchemes.CustomAuthentication) + .AddScheme<AuthenticationSchemeOptions, CustomAuthenticationHandler>(AuthenticationSchemes.CustomAuthentication, null); + } + + /// <summary> + /// Extension method for adding the Jellyfin API to the service collection. + /// </summary> + /// <param name="serviceCollection">The service collection.</param> + /// <param name="pluginAssemblies">An IEnumerable containing all plugin assemblies with API controllers.</param> + /// <param name="config">The <see cref="NetworkConfiguration"/>.</param> + /// <returns>The MVC builder.</returns> + public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, IEnumerable<Assembly> pluginAssemblies, NetworkConfiguration config) + { + IMvcBuilder mvcBuilder = serviceCollection + .AddCors() + .AddTransient<ICorsPolicyProvider, CorsPolicyProvider>() + .Configure<ForwardedHeadersOptions>(options => + { + ConfigureForwardHeaders(config, options); + }) + .AddMvc(opts => + { + // Allow requester to change between camelCase and PascalCase + opts.RespectBrowserAcceptHeader = true; + + opts.OutputFormatters.Insert(0, new CamelCaseJsonProfileFormatter()); + opts.OutputFormatters.Insert(0, new PascalCaseJsonProfileFormatter()); + + opts.OutputFormatters.Add(new CssOutputFormatter()); + opts.OutputFormatters.Add(new XmlOutputFormatter()); + + opts.ModelBinderProviders.Insert(0, new NullableEnumModelBinderProvider()); + }) + + // Clear app parts to avoid other assemblies being picked up + .ConfigureApplicationPartManager(a => a.ApplicationParts.Clear()) + .AddApplicationPart(typeof(StartupController).Assembly) + .AddJsonOptions(options => + { + // Update all properties that are set in JsonDefaults + var jsonOptions = JsonDefaults.PascalCaseOptions; + + // From JsonDefaults + options.JsonSerializerOptions.ReadCommentHandling = jsonOptions.ReadCommentHandling; + options.JsonSerializerOptions.WriteIndented = jsonOptions.WriteIndented; + options.JsonSerializerOptions.DefaultIgnoreCondition = jsonOptions.DefaultIgnoreCondition; + options.JsonSerializerOptions.NumberHandling = jsonOptions.NumberHandling; + + options.JsonSerializerOptions.Converters.Clear(); + foreach (var converter in jsonOptions.Converters) + { + options.JsonSerializerOptions.Converters.Add(converter); + } + + // From JsonDefaults.PascalCase + options.JsonSerializerOptions.PropertyNamingPolicy = jsonOptions.PropertyNamingPolicy; + }); + + foreach (Assembly pluginAssembly in pluginAssemblies) + { + mvcBuilder.AddApplicationPart(pluginAssembly); + } + + return mvcBuilder.AddControllersAsServices(); + } + + internal static void ConfigureForwardHeaders(NetworkConfiguration config, ForwardedHeadersOptions options) + { + // https://github.com/dotnet/aspnetcore/blob/master/src/Middleware/HttpOverrides/src/ForwardedHeadersMiddleware.cs + // Enable debug logging on Microsoft.AspNetCore.HttpOverrides.ForwardedHeadersMiddleware to help investigate issues. + + if (config.KnownProxies.Length == 0) + { + options.ForwardedHeaders = ForwardedHeaders.None; + options.KnownIPNetworks.Clear(); + options.KnownProxies.Clear(); + } + else + { + options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost; + AddProxyAddresses(config, config.KnownProxies, options); + } + + // Only set forward limit if we have some known proxies or some known networks. + if (options.KnownProxies.Count != 0 || options.KnownIPNetworks.Count != 0) + { + options.ForwardLimit = null; + } + } + + /// <summary> + /// Adds Swagger to the service collection. + /// </summary> + /// <param name="serviceCollection">The service collection.</param> + /// <returns>The updated service collection.</returns> + public static IServiceCollection AddJellyfinApiSwagger(this IServiceCollection serviceCollection) + { + return serviceCollection.AddSwaggerGen(c => + { + var version = typeof(ApplicationHost).Assembly.GetName().Version?.ToString(3) ?? "0.0.1"; + c.SwaggerDoc("api-docs", new OpenApiInfo + { + Title = "Jellyfin API", + Version = version, + Extensions = new Dictionary<string, IOpenApiExtension> + { + { + "x-jellyfin-version", + new OpenApiString(version) + } + } + }); + + c.AddSecurityDefinition(AuthenticationSchemes.CustomAuthentication, new OpenApiSecurityScheme + { + Type = SecuritySchemeType.ApiKey, + In = ParameterLocation.Header, + Name = "Authorization", + Description = "API key header parameter" + }); + + // Add all xml doc files to swagger generator. + var xmlFiles = Directory.EnumerateFiles( + AppContext.BaseDirectory, + "*.xml", + SearchOption.TopDirectoryOnly); + + foreach (var xmlFile in xmlFiles) + { + c.IncludeXmlComments(xmlFile); + } + + // Order actions by route path, then by http method. + c.OrderActionsBy(description => + $"{description.ActionDescriptor.RouteValues["controller"]}_{description.RelativePath}"); + + // Use method name as operationId + c.CustomOperationIds( + description => + { + description.TryGetMethodInfo(out MethodInfo methodInfo); + // Attribute name, method name, none. + return description?.ActionDescriptor.AttributeRouteInfo?.Name + ?? methodInfo?.Name + ?? null; + }); + + // Allow parameters to properly be nullable. + c.UseAllOfToExtendReferenceSchemas(); + c.SupportNonNullableReferenceTypes(); + + // TODO - remove when all types are supported in System.Text.Json + c.AddSwaggerTypeMappings(); + + c.SchemaFilter<IgnoreEnumSchemaFilter>(); + c.SchemaFilter<FlagsEnumSchemaFilter>(); + c.OperationFilter<RetryOnTemporarilyUnavailableFilter>(); + c.OperationFilter<SecurityRequirementsOperationFilter>(); + c.OperationFilter<FileResponseFilter>(); + c.OperationFilter<FileRequestFilter>(); + c.OperationFilter<ParameterObsoleteFilter>(); + c.DocumentFilter<AdditionalModelFilter>(); + }) + .Replace(ServiceDescriptor.Transient<ISwaggerProvider, CachingOpenApiProvider>()); + } + + private static void AddPolicy(this AuthorizationOptions authorizationOptions, string policyName, IAuthorizationRequirement authorizationRequirement) + { + authorizationOptions.AddPolicy(policyName, policy => + { + policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication).AddRequirements(authorizationRequirement); + }); + } + + /// <summary> + /// Sets up the proxy configuration based on the addresses/subnets in <paramref name="allowedProxies"/>. + /// </summary> + /// <param name="config">The <see cref="NetworkConfiguration"/> containing the config settings.</param> + /// <param name="allowedProxies">The string array to parse.</param> + /// <param name="options">The <see cref="ForwardedHeadersOptions"/> instance.</param> + internal static void AddProxyAddresses(NetworkConfiguration config, string[] allowedProxies, ForwardedHeadersOptions options) + { + for (var i = 0; i < allowedProxies.Length; i++) + { + if (IPAddress.TryParse(allowedProxies[i], out var addr)) + { + AddIPAddress(config, options, addr, addr.AddressFamily == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : NetworkConstants.MinimumIPv6PrefixSize); + } + else if (NetworkUtils.TryParseToSubnet(allowedProxies[i], out var subnet)) + { + AddIPAddress(config, options, subnet.Address, subnet.Subnet.PrefixLength); + } + else if (NetworkUtils.TryParseHost(allowedProxies[i], out var addresses, config.EnableIPv4, config.EnableIPv6)) + { + foreach (var address in addresses) + { + AddIPAddress(config, options, address, address.AddressFamily == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : NetworkConstants.MinimumIPv6PrefixSize); + } + } + } + } + + private static void AddIPAddress(NetworkConfiguration config, ForwardedHeadersOptions options, IPAddress addr, int prefixLength) + { + if (addr.IsIPv4MappedToIPv6) + { + addr = addr.MapToIPv4(); + } + + if ((!config.EnableIPv4 && addr.AddressFamily == AddressFamily.InterNetwork) || (!config.EnableIPv6 && addr.AddressFamily == AddressFamily.InterNetworkV6)) + { + return; + } + + if (prefixLength == NetworkConstants.MinimumIPv4PrefixSize) + { + options.KnownProxies.Add(addr); + } + else + { + options.KnownIPNetworks.Add(new System.Net.IPNetwork(addr, prefixLength)); + } + } + + private static void AddSwaggerTypeMappings(this SwaggerGenOptions options) + { + /* + * TODO remove when System.Text.Json properly supports non-string keys. + * Used in BaseItemDto.ImageBlurHashes + */ + options.MapType<Dictionary<ImageType, string>>(() => + new OpenApiSchema + { + Type = "object", + AdditionalProperties = new OpenApiSchema + { + Type = "string" + } + }); + + // Support dictionary with nullable string value. + options.MapType<Dictionary<string, string?>>(() => + new OpenApiSchema + { + Type = "object", + AdditionalProperties = new OpenApiSchema + { + Type = "string", + Nullable = true + } + }); + + // Swashbuckle doesn't use JsonOptions to describe responses, so we need to manually describe it. + options.MapType<Version>(() => new OpenApiSchema + { + Type = "string" + }); + } + } +} diff --git a/Jellyfin.Server/Extensions/WebHostBuilderExtensions.cs b/Jellyfin.Server/Extensions/WebHostBuilderExtensions.cs new file mode 100644 index 00000000..be9cf0f1 --- /dev/null +++ b/Jellyfin.Server/Extensions/WebHostBuilderExtensions.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Security.Cryptography.X509Certificates; +using Jellyfin.Server.Helpers; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Extensions; +using MediaBrowser.Model.Net; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Extensions; + +/// <summary> +/// Extensions for configuring the web host builder. +/// </summary> +public static class WebHostBuilderExtensions +{ + /// <summary> + /// Configure the web host builder. + /// </summary> + /// <param name="builder">The builder to configure.</param> + /// <param name="appHost">The application host.</param> + /// <param name="startupConfig">The application configuration.</param> + /// <param name="appPaths">The application paths.</param> + /// <param name="logger">The logger.</param> + /// <returns>The configured web host builder.</returns> + public static IWebHostBuilder ConfigureWebHostBuilder( + this IWebHostBuilder builder, + CoreAppHost appHost, + IConfiguration startupConfig, + IApplicationPaths appPaths, + ILogger logger) + { + return builder + .UseKestrel((builderContext, options) => + { + SetupJellyfinWebServer( + appHost.NetManager.GetAllBindInterfaces(false), + appHost.HttpPort, + appHost.ListenWithHttps ? appHost.HttpsPort : null, + appHost.Certificate, + startupConfig, + appPaths, + logger, + builderContext, + options); + }) + .UseStartup(context => new Startup(appHost, context.Configuration)); + } + + /// <summary> + /// Configures a Kestrel type webServer to bind to the specific arguments. + /// </summary> + /// <param name="addresses">The IP addresses that should be listend to.</param> + /// <param name="httpPort">The http port.</param> + /// <param name="httpsPort">If set the https port. If set you must also set the certificate.</param> + /// <param name="certificate">The certificate used for https port.</param> + /// <param name="startupConfig">The startup config.</param> + /// <param name="appPaths">The app paths.</param> + /// <param name="logger">A logger.</param> + /// <param name="builderContext">The kestrel build pipeline context.</param> + /// <param name="options">The kestrel server options.</param> + /// <exception cref="InvalidOperationException">Will be thrown when a https port is set but no or an invalid certificate is provided.</exception> + public static void SetupJellyfinWebServer( + IReadOnlyList<IPData> addresses, + int httpPort, + int? httpsPort, + X509Certificate2? certificate, + IConfiguration startupConfig, + IApplicationPaths appPaths, + ILogger logger, + WebHostBuilderContext builderContext, + KestrelServerOptions options) + { + bool flagged = false; + foreach (var netAdd in addresses) + { + var address = netAdd.Address; + logger.LogInformation("Kestrel is listening on {Address}", address.Equals(IPAddress.IPv6Any) ? "all interfaces" : address); + options.Listen(netAdd.Address, httpPort); + if (httpsPort.HasValue) + { + if (builderContext.HostingEnvironment.IsDevelopment()) + { + try + { + options.Listen( + address, + httpsPort.Value, + listenOptions => listenOptions.UseHttps()); + } + catch (InvalidOperationException) + { + if (!flagged) + { + logger.LogWarning("Failed to listen to HTTPS using the ASP.NET Core HTTPS development certificate. Please ensure it has been installed and set as trusted"); + flagged = true; + } + } + } + else + { + if (certificate is null) + { + throw new InvalidOperationException("Cannot run jellyfin with https without setting a valid certificate."); + } + + options.Listen( + address, + httpsPort.Value, + listenOptions => listenOptions.UseHttps(certificate)); + } + } + } + + // Bind to unix socket (only on unix systems) + if (startupConfig.UseUnixSocket() && Environment.OSVersion.Platform == PlatformID.Unix) + { + var socketPath = StartupHelpers.GetUnixSocketPath(startupConfig, appPaths); + + // Workaround for https://github.com/aspnet/AspNetCore/issues/14134 + if (File.Exists(socketPath)) + { + File.Delete(socketPath); + } + + options.ListenUnixSocket(socketPath); + logger.LogInformation("Kestrel listening to unix socket {SocketPath}", socketPath); + } + } +} diff --git a/Jellyfin.Server/Filters/AdditionalModelFilter.cs b/Jellyfin.Server/Filters/AdditionalModelFilter.cs new file mode 100644 index 00000000..7407bd2e --- /dev/null +++ b/Jellyfin.Server/Filters/AdditionalModelFilter.cs @@ -0,0 +1,230 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using Jellyfin.Extensions; +using Jellyfin.Server.Migrations; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Net.WebSocketMessages; +using MediaBrowser.Controller.Net.WebSocketMessages.Outbound; +using MediaBrowser.Model.ApiClient; +using MediaBrowser.Model.Session; +using MediaBrowser.Model.SyncPlay; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Jellyfin.Server.Filters +{ + /// <summary> + /// Add models not directly used by the API, but used for discovery and websockets. + /// </summary> + public class AdditionalModelFilter : IDocumentFilter + { + // Array of options that should not be visible in the api spec. + private static readonly Type[] _ignoredConfigurations = { typeof(MigrationOptions), typeof(MediaBrowser.Model.Branding.BrandingOptions) }; + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="AdditionalModelFilter"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + public AdditionalModelFilter(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// <inheritdoc /> + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + context.SchemaGenerator.GenerateSchema(typeof(IPlugin), context.SchemaRepository); + + var webSocketTypes = typeof(WebSocketMessage).Assembly.GetTypes() + .Where(t => t.IsSubclassOf(typeof(WebSocketMessage)) + && !t.IsGenericType + && t != typeof(WebSocketMessageInfo)) + .ToList(); + + var inboundWebSocketSchemas = new List<OpenApiSchema>(); + var inboundWebSocketDiscriminators = new Dictionary<string, string>(); + foreach (var type in webSocketTypes.Where(t => typeof(IInboundWebSocketMessage).IsAssignableFrom(t))) + { + var messageType = (SessionMessageType?)type.GetProperty(nameof(WebSocketMessage.MessageType))?.GetCustomAttribute<DefaultValueAttribute>()?.Value; + if (messageType is null) + { + continue; + } + + var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository); + inboundWebSocketSchemas.Add(schema); + inboundWebSocketDiscriminators[messageType.ToString()!] = schema.Reference.ReferenceV3; + } + + var inboundWebSocketMessageSchema = new OpenApiSchema + { + Type = "object", + Description = "Represents the list of possible inbound websocket types", + Reference = new OpenApiReference + { + Id = nameof(InboundWebSocketMessage), + Type = ReferenceType.Schema + }, + OneOf = inboundWebSocketSchemas, + Discriminator = new OpenApiDiscriminator + { + PropertyName = nameof(WebSocketMessage.MessageType), + Mapping = inboundWebSocketDiscriminators + } + }; + + context.SchemaRepository.AddDefinition(nameof(InboundWebSocketMessage), inboundWebSocketMessageSchema); + + var outboundWebSocketSchemas = new List<OpenApiSchema>(); + var outboundWebSocketDiscriminators = new Dictionary<string, string>(); + foreach (var type in webSocketTypes.Where(t => typeof(IOutboundWebSocketMessage).IsAssignableFrom(t))) + { + var messageType = (SessionMessageType?)type.GetProperty(nameof(WebSocketMessage.MessageType))?.GetCustomAttribute<DefaultValueAttribute>()?.Value; + if (messageType is null) + { + continue; + } + + var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository); + outboundWebSocketSchemas.Add(schema); + outboundWebSocketDiscriminators.Add(messageType.ToString()!, schema.Reference.ReferenceV3); + } + + // Add custom "SyncPlayGroupUpdateMessage" schema because Swashbuckle cannot generate it for us + var syncPlayGroupUpdateMessageSchema = new OpenApiSchema + { + Type = "object", + Description = "Untyped sync play command.", + Properties = new Dictionary<string, OpenApiSchema> + { + { + "Data", new OpenApiSchema + { + AllOf = + [ + new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = nameof(GroupUpdate<object>) } } + ], + Description = "Group update data", + Nullable = false, + } + }, + { "MessageId", new OpenApiSchema { Type = "string", Format = "uuid", Description = "Gets or sets the message id." } }, + { + "MessageType", new OpenApiSchema + { + Enum = Enum.GetValues<SessionMessageType>().Select(type => new OpenApiString(type.ToString())).ToList<IOpenApiAny>(), + AllOf = + [ + new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = nameof(SessionMessageType) } } + ], + Description = "The different kinds of messages that are used in the WebSocket api.", + Default = new OpenApiString(nameof(SessionMessageType.SyncPlayGroupUpdate)), + ReadOnly = true + } + }, + }, + AdditionalPropertiesAllowed = false, + Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "SyncPlayGroupUpdateMessage" } + }; + context.SchemaRepository.AddDefinition("SyncPlayGroupUpdateMessage", syncPlayGroupUpdateMessageSchema); + outboundWebSocketSchemas.Add(syncPlayGroupUpdateMessageSchema); + outboundWebSocketDiscriminators[nameof(SessionMessageType.SyncPlayGroupUpdate)] = syncPlayGroupUpdateMessageSchema.Reference.ReferenceV3; + + var outboundWebSocketMessageSchema = new OpenApiSchema + { + Type = "object", + Description = "Represents the list of possible outbound websocket types", + Reference = new OpenApiReference + { + Id = nameof(OutboundWebSocketMessage), + Type = ReferenceType.Schema + }, + OneOf = outboundWebSocketSchemas, + Discriminator = new OpenApiDiscriminator + { + PropertyName = nameof(WebSocketMessage.MessageType), + Mapping = outboundWebSocketDiscriminators + } + }; + + context.SchemaRepository.AddDefinition(nameof(OutboundWebSocketMessage), outboundWebSocketMessageSchema); + context.SchemaRepository.AddDefinition( + nameof(WebSocketMessage), + new OpenApiSchema + { + Type = "object", + Description = "Represents the possible websocket types", + Reference = new OpenApiReference + { + Id = nameof(WebSocketMessage), + Type = ReferenceType.Schema + }, + OneOf = new[] + { + inboundWebSocketMessageSchema, + outboundWebSocketMessageSchema + } + }); + + // Manually generate sync play GroupUpdate messages. + var groupUpdateTypes = typeof(GroupUpdate<>).Assembly.GetTypes() + .Where(t => t.BaseType is not null + && t.BaseType.IsGenericType + && t.BaseType.GetGenericTypeDefinition() == typeof(GroupUpdate<>)) + .ToList(); + + var groupUpdateSchemas = new List<OpenApiSchema>(); + var groupUpdateDiscriminators = new Dictionary<string, string>(); + foreach (var type in groupUpdateTypes) + { + var groupUpdateType = (GroupUpdateType?)type.GetProperty(nameof(GroupUpdate<object>.Type))?.GetCustomAttribute<DefaultValueAttribute>()?.Value; + if (groupUpdateType is null) + { + continue; + } + + var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository); + groupUpdateSchemas.Add(schema); + groupUpdateDiscriminators[groupUpdateType.ToString()!] = schema.Reference.ReferenceV3; + } + + var groupUpdateSchema = new OpenApiSchema + { + Type = "object", + Description = "Represents the list of possible group update types", + Reference = new OpenApiReference + { + Id = nameof(GroupUpdate<object>), + Type = ReferenceType.Schema + }, + OneOf = groupUpdateSchemas, + Discriminator = new OpenApiDiscriminator + { + PropertyName = nameof(GroupUpdate<object>.Type), + Mapping = groupUpdateDiscriminators + } + }; + + context.SchemaRepository.Schemas[nameof(GroupUpdate<object>)] = groupUpdateSchema; + + context.SchemaGenerator.GenerateSchema(typeof(ServerDiscoveryInfo), context.SchemaRepository); + + foreach (var configuration in _serverConfigurationManager.GetConfigurationStores()) + { + if (_ignoredConfigurations.IndexOf(configuration.ConfigurationType) != -1) + { + continue; + } + + context.SchemaGenerator.GenerateSchema(configuration.ConfigurationType, context.SchemaRepository); + } + } + } +} diff --git a/Jellyfin.Server/Filters/CachingOpenApiProvider.cs b/Jellyfin.Server/Filters/CachingOpenApiProvider.cs new file mode 100644 index 00000000..833b6844 --- /dev/null +++ b/Jellyfin.Server/Filters/CachingOpenApiProvider.cs @@ -0,0 +1,93 @@ +using System; +using AsyncKeyedLock; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Jellyfin.Server.Filters; + +/// <summary> +/// OpenApi provider with caching. +/// </summary> +internal sealed class CachingOpenApiProvider : ISwaggerProvider +{ + private const string CacheKey = "openapi.json"; + + private static readonly MemoryCacheEntryOptions _cacheOptions = new() { SlidingExpiration = TimeSpan.FromMinutes(5) }; + private static readonly AsyncNonKeyedLocker _lock = new(1); + private static readonly TimeSpan _lockTimeout = TimeSpan.FromSeconds(1); + + private readonly IMemoryCache _memoryCache; + private readonly SwaggerGenerator _swaggerGenerator; + private readonly SwaggerGeneratorOptions _swaggerGeneratorOptions; + private readonly ILogger<CachingOpenApiProvider> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="CachingOpenApiProvider"/> class. + /// </summary> + /// <param name="optionsAccessor">The options accessor.</param> + /// <param name="apiDescriptionsProvider">The api descriptions provider.</param> + /// <param name="schemaGenerator">The schema generator.</param> + /// <param name="memoryCache">The memory cache.</param> + /// <param name="logger">The logger.</param> + public CachingOpenApiProvider( + IOptions<SwaggerGeneratorOptions> optionsAccessor, + IApiDescriptionGroupCollectionProvider apiDescriptionsProvider, + ISchemaGenerator schemaGenerator, + IMemoryCache memoryCache, + ILogger<CachingOpenApiProvider> logger) + { + _swaggerGeneratorOptions = optionsAccessor.Value; + _swaggerGenerator = new SwaggerGenerator(_swaggerGeneratorOptions, apiDescriptionsProvider, schemaGenerator); + _memoryCache = memoryCache; + _logger = logger; + } + + /// <inheritdoc /> + public OpenApiDocument GetSwagger(string documentName, string? host = null, string? basePath = null) + { + if (_memoryCache.TryGetValue(CacheKey, out OpenApiDocument? openApiDocument) && openApiDocument is not null) + { + return AdjustDocument(openApiDocument, host, basePath); + } + + using var acquired = _lock.LockOrNull(_lockTimeout); + if (_memoryCache.TryGetValue(CacheKey, out openApiDocument) && openApiDocument is not null) + { + return AdjustDocument(openApiDocument, host, basePath); + } + + if (acquired is null) + { + throw new InvalidOperationException("OpenApi document is generating"); + } + + try + { + openApiDocument = _swaggerGenerator.GetSwagger(documentName); + } + catch (Exception ex) + { + _logger.LogError(ex, "OpenAPI generation error"); + throw; + } + + _memoryCache.Set(CacheKey, openApiDocument, _cacheOptions); + return AdjustDocument(openApiDocument, host, basePath); + } + + private OpenApiDocument AdjustDocument(OpenApiDocument document, string? host, string? basePath) + { + document.Servers = _swaggerGeneratorOptions.Servers.Count != 0 + ? _swaggerGeneratorOptions.Servers + : string.IsNullOrEmpty(host) && string.IsNullOrEmpty(basePath) + ? [] + : [new OpenApiServer { Url = $"{host}{basePath}" }]; + + return document; + } +} diff --git a/Jellyfin.Server/Filters/FileRequestFilter.cs b/Jellyfin.Server/Filters/FileRequestFilter.cs new file mode 100644 index 00000000..86dbf765 --- /dev/null +++ b/Jellyfin.Server/Filters/FileRequestFilter.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using Jellyfin.Api.Attributes; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Jellyfin.Server.Filters +{ + /// <inheritdoc /> + public class FileRequestFilter : IOperationFilter + { + /// <inheritdoc /> + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + foreach (var attribute in context.ApiDescription.ActionDescriptor.EndpointMetadata) + { + if (attribute is AcceptsFileAttribute acceptsFileAttribute) + { + operation.RequestBody = GetRequestBody(acceptsFileAttribute.ContentTypes); + break; + } + } + } + + private static OpenApiRequestBody GetRequestBody(IEnumerable<string> contentTypes) + { + var body = new OpenApiRequestBody(); + var mediaType = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string", + Format = "binary" + } + }; + foreach (var contentType in contentTypes) + { + body.Content.Add(contentType, mediaType); + } + + return body; + } + } +} diff --git a/Jellyfin.Server/Filters/FileResponseFilter.cs b/Jellyfin.Server/Filters/FileResponseFilter.cs new file mode 100644 index 00000000..cd0acadf --- /dev/null +++ b/Jellyfin.Server/Filters/FileResponseFilter.cs @@ -0,0 +1,53 @@ +using System; +using System.Linq; +using Jellyfin.Api.Attributes; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Jellyfin.Server.Filters +{ + /// <inheritdoc /> + public class FileResponseFilter : IOperationFilter + { + private const string SuccessCode = "200"; + private static readonly OpenApiMediaType _openApiMediaType = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string", + Format = "binary" + } + }; + + /// <inheritdoc /> + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + foreach (var attribute in context.ApiDescription.ActionDescriptor.EndpointMetadata) + { + if (attribute is ProducesFileAttribute producesFileAttribute) + { + // Get operation response values. + var response = operation.Responses + .FirstOrDefault(o => o.Key.Equals(SuccessCode, StringComparison.Ordinal)); + + // Operation doesn't have a response. + if (response.Value is null) + { + continue; + } + + // Clear existing responses. + response.Value.Content.Clear(); + + // Add all content-types as file. + foreach (var contentType in producesFileAttribute.ContentTypes) + { + response.Value.Content.Add(contentType, _openApiMediaType); + } + + break; + } + } + } + } +} diff --git a/Jellyfin.Server/Filters/FlagsEnumSchemaFilter.cs b/Jellyfin.Server/Filters/FlagsEnumSchemaFilter.cs new file mode 100644 index 00000000..3e0b69d0 --- /dev/null +++ b/Jellyfin.Server/Filters/FlagsEnumSchemaFilter.cs @@ -0,0 +1,53 @@ +using System; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Jellyfin.Server.Filters; + +/// <summary> +/// Schema filter to ensure flags enums are represented correctly in OpenAPI. +/// </summary> +/// <remarks> +/// For flags enums: +/// - The enum schema definition is set to type "string" (not integer). +/// - Properties using flags enums are transformed to arrays referencing the enum schema. +/// </remarks> +public class FlagsEnumSchemaFilter : ISchemaFilter +{ + /// <inheritdoc /> + public void Apply(OpenApiSchema schema, SchemaFilterContext context) + { + var type = context.Type.IsEnum ? context.Type : Nullable.GetUnderlyingType(context.Type); + if (type is null || !type.IsEnum) + { + return; + } + + // Check if enum has [Flags] attribute + if (!type.IsDefined(typeof(FlagsAttribute), false)) + { + return; + } + + if (context.MemberInfo is null) + { + // Processing the enum definition itself - ensure it's type "string" not "integer" + schema.Type = "string"; + schema.Format = null; + } + else + { + // Processing a property that uses the flags enum - transform to array + // Generate the enum schema to ensure it exists in the repository + var enumSchema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository); + + // Flags enums should be represented as arrays referencing the enum schema + // since multiple values can be combined + schema.Type = "array"; + schema.Format = null; + schema.Enum = null; + schema.AllOf = null; + schema.Items = enumSchema; + } + } +} diff --git a/Jellyfin.Server/Filters/IgnoreEnumSchemaFilter.cs b/Jellyfin.Server/Filters/IgnoreEnumSchemaFilter.cs new file mode 100644 index 00000000..eb9ad03c --- /dev/null +++ b/Jellyfin.Server/Filters/IgnoreEnumSchemaFilter.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Jellyfin.Data.Attributes; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Jellyfin.Server.Filters; + +/// <summary> +/// Filter to remove ignored enum values. +/// </summary> +public class IgnoreEnumSchemaFilter : ISchemaFilter +{ + /// <inheritdoc /> + public void Apply(OpenApiSchema schema, SchemaFilterContext context) + { + if (context.Type.IsEnum || (Nullable.GetUnderlyingType(context.Type)?.IsEnum ?? false)) + { + var type = context.Type.IsEnum ? context.Type : Nullable.GetUnderlyingType(context.Type); + if (type is null) + { + return; + } + + var enumOpenApiStrings = new List<IOpenApiAny>(); + + foreach (var enumName in Enum.GetNames(type)) + { + var member = type.GetMember(enumName)[0]; + if (!member.GetCustomAttributes<OpenApiIgnoreEnumAttribute>().Any()) + { + enumOpenApiStrings.Add(new OpenApiString(enumName)); + } + } + + schema.Enum = enumOpenApiStrings; + } + } +} diff --git a/Jellyfin.Server/Filters/ParameterObsoleteFilter.cs b/Jellyfin.Server/Filters/ParameterObsoleteFilter.cs new file mode 100644 index 00000000..98a8dc0f --- /dev/null +++ b/Jellyfin.Server/Filters/ParameterObsoleteFilter.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq; +using Jellyfin.Api.Attributes; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Jellyfin.Server.Filters +{ + /// <summary> + /// Mark parameter as deprecated if it has the <see cref="ParameterObsoleteAttribute"/>. + /// </summary> + public class ParameterObsoleteFilter : IOperationFilter + { + /// <inheritdoc /> + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + foreach (var parameterDescription in context.ApiDescription.ParameterDescriptions) + { + if (parameterDescription + .CustomAttributes() + .OfType<ParameterObsoleteAttribute>() + .Any()) + { + foreach (var parameter in operation.Parameters) + { + if (parameter.Name.Equals(parameterDescription.Name, StringComparison.Ordinal)) + { + parameter.Deprecated = true; + break; + } + } + } + } + } + } +} diff --git a/Jellyfin.Server/Filters/RetryOnTemporarilyUnavailableFilter.cs b/Jellyfin.Server/Filters/RetryOnTemporarilyUnavailableFilter.cs new file mode 100644 index 00000000..8b726851 --- /dev/null +++ b/Jellyfin.Server/Filters/RetryOnTemporarilyUnavailableFilter.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Jellyfin.Server.Filters; + +internal class RetryOnTemporarilyUnavailableFilter : IOperationFilter +{ + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + operation.Responses.TryAdd( + "503", + new OpenApiResponse + { + Description = "The server is currently starting or is temporarily not available.", + Headers = new Dictionary<string, OpenApiHeader> + { + { + "Retry-After", new OpenApiHeader + { + AllowEmptyValue = true, + Required = false, + Description = "A hint for when to retry the operation in full seconds.", + Schema = new OpenApiSchema + { + Type = "integer", + Format = "int32" + } + } + }, + { + "Message", new OpenApiHeader + { + AllowEmptyValue = true, + Required = false, + Description = "A short plain-text reason why the server is not available.", + Schema = new OpenApiSchema + { + Type = "string", + Format = "text" + } + } + } + }, + Content = new Dictionary<string, OpenApiMediaType>() + { + { "text/html", new OpenApiMediaType() } + } + }); + } +} diff --git a/Jellyfin.Server/Filters/SecurityRequirementsOperationFilter.cs b/Jellyfin.Server/Filters/SecurityRequirementsOperationFilter.cs new file mode 100644 index 00000000..8f575726 --- /dev/null +++ b/Jellyfin.Server/Filters/SecurityRequirementsOperationFilter.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Jellyfin.Api.Auth.DefaultAuthorizationPolicy; +using Jellyfin.Api.Constants; +using Jellyfin.Extensions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Jellyfin.Server.Filters; + +/// <summary> +/// Security requirement operation filter. +/// </summary> +public class SecurityRequirementsOperationFilter : IOperationFilter +{ + private const string DefaultAuthPolicy = "DefaultAuthorization"; + private static readonly Type _attributeType = typeof(AuthorizeAttribute); + + private readonly IAuthorizationPolicyProvider _authorizationPolicyProvider; + + /// <summary> + /// Initializes a new instance of the <see cref="SecurityRequirementsOperationFilter"/> class. + /// </summary> + /// <param name="authorizationPolicyProvider">The authorization policy provider.</param> + public SecurityRequirementsOperationFilter(IAuthorizationPolicyProvider authorizationPolicyProvider) + { + _authorizationPolicyProvider = authorizationPolicyProvider; + } + + /// <inheritdoc /> + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + var requiredScopes = new List<string>(); + + var requiresAuth = false; + // Add all method scopes. + foreach (var authorizeAttribute in context.MethodInfo.GetCustomAttributes(_attributeType, true).Cast<AuthorizeAttribute>()) + { + requiresAuth = true; + var policy = authorizeAttribute.Policy ?? DefaultAuthPolicy; + if (!requiredScopes.Contains(policy, StringComparer.Ordinal)) + { + requiredScopes.Add(policy); + } + } + + // Add controller scopes if any. + var controllerAttributes = context.MethodInfo.DeclaringType?.GetCustomAttributes(_attributeType, true).Cast<AuthorizeAttribute>(); + if (controllerAttributes is not null) + { + foreach (var authorizeAttribute in controllerAttributes) + { + requiresAuth = true; + var policy = authorizeAttribute.Policy ?? DefaultAuthPolicy; + if (!requiredScopes.Contains(policy, StringComparer.Ordinal)) + { + requiredScopes.Add(policy); + } + } + } + + if (!requiresAuth) + { + return; + } + + operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" }); + operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" }); + + var scheme = new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = AuthenticationSchemes.CustomAuthentication + }, + }; + + // Add DefaultAuthorization scope to any endpoint that has a policy with a requirement that is a subset of DefaultAuthorization. + if (!requiredScopes.Contains(DefaultAuthPolicy.AsSpan(), StringComparison.Ordinal)) + { + foreach (var scope in requiredScopes) + { + var authorizationPolicy = _authorizationPolicyProvider.GetPolicyAsync(scope).GetAwaiter().GetResult(); + if (authorizationPolicy is not null + && authorizationPolicy.Requirements.Any(r => r is DefaultAuthorizationRequirement)) + { + requiredScopes.Add(DefaultAuthPolicy); + break; + } + } + } + + operation.Security = [new OpenApiSecurityRequirement { [scheme] = requiredScopes }]; + } +} diff --git a/Jellyfin.Server/HealthChecks/DbContextFactoryHealthCheck.cs b/Jellyfin.Server/HealthChecks/DbContextFactoryHealthCheck.cs new file mode 100644 index 00000000..bf00dcd5 --- /dev/null +++ b/Jellyfin.Server/HealthChecks/DbContextFactoryHealthCheck.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace Jellyfin.Server.HealthChecks; + +/// <summary> +/// Implementation of the <see cref="DbContextHealthCheck{TContext}"/> for a <see cref="IDbContextFactory{TContext}"/>. +/// </summary> +/// <typeparam name="TContext">The type of database context.</typeparam> +public class DbContextFactoryHealthCheck<TContext> : IHealthCheck + where TContext : DbContext +{ + private readonly IDbContextFactory<TContext> _dbContextFactory; + + /// <summary> + /// Initializes a new instance of the <see cref="DbContextFactoryHealthCheck{TContext}"/> class. + /// </summary> + /// <param name="contextFactory">Instance of the <see cref="IDbContextFactory{TContext}"/> interface.</param> + public DbContextFactoryHealthCheck(IDbContextFactory<TContext> contextFactory) + { + _dbContextFactory = contextFactory; + } + + /// <inheritdoc /> + public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(context); + + var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + if (await dbContext.Database.CanConnectAsync(cancellationToken).ConfigureAwait(false)) + { + return HealthCheckResult.Healthy(); + } + } + + return HealthCheckResult.Unhealthy(); + } +} diff --git a/Jellyfin.Server/Helpers/StartupHelpers.cs b/Jellyfin.Server/Helpers/StartupHelpers.cs new file mode 100644 index 00000000..93c99616 --- /dev/null +++ b/Jellyfin.Server/Helpers/StartupHelpers.cs @@ -0,0 +1,300 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Text; +using System.Threading.Tasks; +using Emby.Server.Implementations; +using Jellyfin.Server.ServerSetupApp; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Extensions; +using MediaBrowser.Model.IO; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Serilog; +using Serilog.Extensions.Logging; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace Jellyfin.Server.Helpers; + +/// <summary> +/// A class containing helper methods for server startup. +/// </summary> +public static class StartupHelpers +{ + private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" }; + + /// <summary> + /// Logs relevant environment variables and information about the host. + /// </summary> + /// <param name="logger">The logger to use.</param> + /// <param name="appPaths">The application paths to use.</param> + public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths) + { + // Distinct these to prevent users from reporting problems that aren't actually problems + var commandLineArgs = Environment + .GetCommandLineArgs() + .Distinct(); + + // Get all relevant environment variables + var allEnvVars = Environment.GetEnvironmentVariables(); + var relevantEnvVars = new Dictionary<object, object>(); + foreach (var key in allEnvVars.Keys) + { + if (_relevantEnvVarPrefixes.Any(prefix => key.ToString()!.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))) + { + relevantEnvVars.Add(key, allEnvVars[key]!); + } + } + + logger.LogInformation("Environment Variables: {EnvVars}", relevantEnvVars); + logger.LogInformation("Arguments: {Args}", commandLineArgs); + logger.LogInformation("Operating system: {OS}", RuntimeInformation.OSDescription); + logger.LogInformation("Architecture: {Architecture}", RuntimeInformation.OSArchitecture); + logger.LogInformation("64-Bit Process: {Is64Bit}", Environment.Is64BitProcess); + logger.LogInformation("User Interactive: {IsUserInteractive}", Environment.UserInteractive); + logger.LogInformation("Processor count: {ProcessorCount}", Environment.ProcessorCount); + logger.LogInformation("Program data path: {ProgramDataPath}", appPaths.ProgramDataPath); + logger.LogInformation("Log directory path: {LogDirectoryPath}", appPaths.LogDirectoryPath); + logger.LogInformation("Config directory path: {ConfigurationDirectoryPath}", appPaths.ConfigurationDirectoryPath); + logger.LogInformation("Cache path: {CachePath}", appPaths.CachePath); + logger.LogInformation("Temp directory path: {TempDirPath}", appPaths.TempDirectory); + logger.LogInformation("Web resources path: {WebPath}", appPaths.WebPath); + logger.LogInformation("Application directory: {ApplicationPath}", appPaths.ProgramSystemPath); + } + + /// <summary> + /// Create the data, config and log paths from the variety of inputs(command line args, + /// environment variables) or decide on what default to use. For Windows it's %AppPath% + /// for everything else the + /// <a href="https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html">XDG approach</a> + /// is followed. + /// </summary> + /// <param name="options">The <see cref="StartupOptions" /> for this instance.</param> + /// <returns><see cref="ServerApplicationPaths" />.</returns> + public static ServerApplicationPaths CreateApplicationPaths(StartupOptions options) + { + // LocalApplicationData + // Windows: %LocalAppData% + // macOS: NSApplicationSupportDirectory + // UNIX: $XDG_DATA_HOME + var dataDir = options.DataDir + ?? Environment.GetEnvironmentVariable("JELLYFIN_DATA_DIR") + ?? Path.Join( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData, Environment.SpecialFolderOption.DoNotVerify), + "jellyfin"); + + var configDir = options.ConfigDir ?? Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR"); + if (configDir is null) + { + configDir = Path.Join(dataDir, "config"); + if (options.DataDir is null + && !Directory.Exists(configDir) + && !OperatingSystem.IsWindows() + && !OperatingSystem.IsMacOS()) + { + // UNIX: $XDG_CONFIG_HOME + configDir = Path.Join( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.DoNotVerify), + "jellyfin"); + } + } + + var cacheDir = options.CacheDir ?? Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR"); + if (cacheDir is null) + { + if (OperatingSystem.IsWindows() || OperatingSystem.IsMacOS()) + { + cacheDir = Path.Join(dataDir, "cache"); + } + else + { + cacheDir = Path.Join(GetXdgCacheHome(), "jellyfin"); + } + } + + var webDir = options.WebDir ?? Environment.GetEnvironmentVariable("JELLYFIN_WEB_DIR"); + if (webDir is null) + { + webDir = Path.Join(AppContext.BaseDirectory, "jellyfin-web"); + } + + var logDir = options.LogDir ?? Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR"); + if (logDir is null) + { + logDir = Path.Join(dataDir, "log"); + } + + // Normalize paths. Only possible with GetFullPath for now - https://github.com/dotnet/runtime/issues/2162 + dataDir = Path.GetFullPath(dataDir); + logDir = Path.GetFullPath(logDir); + configDir = Path.GetFullPath(configDir); + cacheDir = Path.GetFullPath(cacheDir); + webDir = Path.GetFullPath(webDir); + + // Ensure the main folders exist before we continue + try + { + Directory.CreateDirectory(dataDir); + Directory.CreateDirectory(logDir); + Directory.CreateDirectory(configDir); + Directory.CreateDirectory(cacheDir); + } + catch (IOException ex) + { + Console.Error.WriteLine("Error whilst attempting to create folder"); + Console.Error.WriteLine(ex.ToString()); + Environment.Exit(1); + } + + return new ServerApplicationPaths(dataDir, logDir, configDir, cacheDir, webDir); + } + + private static string GetXdgCacheHome() + { + // $XDG_CACHE_HOME defines the base directory relative to which + // user specific non-essential data files should be stored. + var cacheHome = Environment.GetEnvironmentVariable("XDG_CACHE_HOME"); + + // If $XDG_CACHE_HOME is either not set or a relative path, + // a default equal to $HOME/.cache should be used. + if (cacheHome is null || !cacheHome.StartsWith('/')) + { + cacheHome = Path.Join( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile, Environment.SpecialFolderOption.DoNotVerify), + ".cache"); + } + + return cacheHome; + } + + /// <summary> + /// Gets the path for the unix socket Kestrel should bind to. + /// </summary> + /// <param name="startupConfig">The startup config.</param> + /// <param name="appPaths">The application paths.</param> + /// <returns>The path for Kestrel to bind to.</returns> + public static string GetUnixSocketPath(IConfiguration startupConfig, IApplicationPaths appPaths) + { + var socketPath = startupConfig.GetUnixSocketPath(); + + if (string.IsNullOrEmpty(socketPath)) + { + const string SocketFile = "jellyfin.sock"; + + var xdgRuntimeDir = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR"); + if (xdgRuntimeDir is null) + { + // Fall back to config dir + socketPath = Path.Join(appPaths.ConfigurationDirectoryPath, SocketFile); + } + else + { + socketPath = Path.Join(xdgRuntimeDir, SocketFile); + } + } + + return socketPath; + } + + /// <summary> + /// Sets the unix file permissions for Kestrel's socket file. + /// </summary> + /// <param name="startupConfig">The startup config.</param> + /// <param name="socketPath">The socket path.</param> + /// <param name="logger">The logger.</param> + [UnsupportedOSPlatform("windows")] + public static void SetUnixSocketPermissions(IConfiguration startupConfig, string socketPath, ILogger logger) + { + var socketPerms = startupConfig.GetUnixSocketPermissions(); + + if (!string.IsNullOrEmpty(socketPerms)) + { + File.SetUnixFileMode(socketPath, (UnixFileMode)Convert.ToInt32(socketPerms, 8)); + logger.LogInformation("Kestrel unix socket permissions set to {SocketPerms}", socketPerms); + } + } + + /// <summary> + /// Initialize the logging configuration file using the bundled resource file as a default if it doesn't exist + /// already. + /// </summary> + /// <param name="appPaths">The application paths.</param> + /// <returns>A task representing the creation of the configuration file, or a completed task if the file already exists.</returns> + public static async Task InitLoggingConfigFile(IApplicationPaths appPaths) + { + // Do nothing if the config file already exists + string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, Program.LoggingConfigFileDefault); + if (File.Exists(configPath)) + { + return; + } + + // Get a stream of the resource contents + // NOTE: The .csproj name is used instead of the assembly name in the resource path + const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json"; + Stream resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath) + ?? throw new InvalidOperationException($"Invalid resource path: '{ResourcePath}'"); + await using (resource.ConfigureAwait(false)) + { + Stream dst = new FileStream(configPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); + await using (dst.ConfigureAwait(false)) + { + // Copy the resource contents to the expected file path for the config file + await resource.CopyToAsync(dst).ConfigureAwait(false); + } + } + } + + /// <summary> + /// Initialize Serilog using configuration and fall back to defaults on failure. + /// </summary> + /// <param name="configuration">The configuration object.</param> + /// <param name="appPaths">The application paths.</param> + public static void InitializeLoggingFramework(IConfiguration configuration, IApplicationPaths appPaths) + { + try + { + var startupLogger = new LoggerProviderCollection(); + startupLogger.AddProvider(new SetupServer.SetupLoggerFactory()); + // Serilog.Log is used by SerilogLoggerFactory when no logger is specified + Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(configuration) + .Enrich.FromLogContext() + .Enrich.WithThreadId() + .WriteTo.Async(e => e.Providers(startupLogger)) + .CreateLogger(); + } + catch (Exception ex) + { + Log.Logger = new LoggerConfiguration() + .WriteTo.Console( + outputTemplate: "[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}", + formatProvider: CultureInfo.InvariantCulture) + .WriteTo.Async(x => x.File( + Path.Combine(appPaths.LogDirectoryPath, "log_.log"), + rollingInterval: RollingInterval.Day, + outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}", + formatProvider: CultureInfo.InvariantCulture, + encoding: Encoding.UTF8)) + .Enrich.FromLogContext() + .Enrich.WithThreadId() + .CreateLogger(); + + Log.Logger.Fatal(ex, "Failed to create/read logger configuration"); + } + } + + /// <summary> + /// Call static initialization methods for the application. + /// </summary> + public static void PerformStaticInitialization() + { + // Make sure we have all the code pages we can get + // Ref: https://docs.microsoft.com/en-us/dotnet/api/system.text.codepagesencodingprovider.instance?view=netcore-3.0#remarks + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + } +} diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj new file mode 100644 index 00000000..9f5bf01a --- /dev/null +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -0,0 +1,86 @@ +<Project Sdk="Microsoft.NET.Sdk.Web"> + + <!-- ProjectGuid is only included as a requirement for SonarQube analysis --> + <PropertyGroup> + <ProjectGuid>{07E39F42-A2C6-4B32-AF8C-725F957A73FF}</ProjectGuid> + </PropertyGroup> + + <PropertyGroup> + <AssemblyName>jellyfin</AssemblyName> + <OutputType>Exe</OutputType> + <TargetFramework>net10.0</TargetFramework> + <ServerGarbageCollection>false</ServerGarbageCollection> + <GenerateAssemblyInfo>false</GenerateAssemblyInfo> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + <ApplicationIcon>Jellyfin.Server.ico</ApplicationIcon> + </PropertyGroup> + + <ItemGroup> + <Compile Include="..\SharedVersion.cs" /> + </ItemGroup> + + <ItemGroup> + <Content Include="Jellyfin.Server.ico" /> + </ItemGroup> + + <ItemGroup> + <EmbeddedResource Include="Resources/Configuration/*" /> + </ItemGroup> + + <!-- Code Analyzers --> + <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="IDisposableAnalyzers"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> + <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="CommandLineParser" /> + <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" /> + <PackageReference Include="Morestachio" /> + <PackageReference Include="prometheus-net" /> + <PackageReference Include="prometheus-net.AspNetCore" /> + <PackageReference Include="Serilog.AspNetCore" /> + <PackageReference Include="Serilog.Enrichers.Thread" /> + <PackageReference Include="Serilog.Expressions" /> + <PackageReference Include="Serilog.Settings.Configuration" /> + <PackageReference Include="Serilog.Sinks.Async" /> + <PackageReference Include="Serilog.Sinks.Console" /> + <PackageReference Include="Serilog.Sinks.File" /> + <PackageReference Include="Serilog.Sinks.Graylog" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\src\Jellyfin.Drawing\Jellyfin.Drawing.csproj" /> + <ProjectReference Include="..\Emby.Server.Implementations\Emby.Server.Implementations.csproj" /> + <ProjectReference Include="..\src\Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj" /> + <ProjectReference Include="..\src\Jellyfin.LiveTv\Jellyfin.LiveTv.csproj" /> + <ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" /> + <ProjectReference Include="..\src\Jellyfin.MediaEncoding.Hls\Jellyfin.MediaEncoding.Hls.csproj" /> + <ProjectReference Include="..\src\Jellyfin.Database\Jellyfin.Database.Implementations\Jellyfin.Database.Implementations.csproj" /> + </ItemGroup> + + <ItemGroup> + <None Update="wwwroot\api-docs\redoc\custom.css"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </None> + <None Update="wwwroot\api-docs\swagger\custom.css"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </None> + <None Update="wwwroot\api-docs\jellyfin.svg"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </None> + <None Update="ServerSetupApp/index.mstemplate.html"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </None> + </ItemGroup> + +</Project> diff --git a/Jellyfin.Server/Jellyfin.Server.ico b/Jellyfin.Server/Jellyfin.Server.ico new file mode 100644 index 0000000000000000000000000000000000000000..0872b956a9fbbfa14c10ea74368f050ed40d1241 GIT binary patch literal 40883 zcmdSB2|QHo`#*l{va6&;(Qc`<P(+3bX|ZH0$r4gz3E3I4MIs?f_7<U{R9YDn*|%)j zD^d3BhME6$pHWZG^7(u}>+k>lp67L(nQ6{_uKT{W_w~N+c`%F)W5RfNFt`d~XBaU| z2g9(XOKI;fEx@o`_)JKM_Wmw>Pl*e|wr-t&zaGN|SHTT%p!-*zz_9E4F-#cV!&l)G z+UIb=Fc@d;7TSrwhd(9O9$5K#N4R>jmY#Q9S?+jFqQcc!sfwthRpWD5ulACH-uvKP zTJ>R@l^P@0NHoPPTlpcLo3++o9&1Tq!rq!hvlp9(o_c5L_o&p?wW-X(rmw=qY`n@t zZ>IX9+H9@A;%wdJ-81!}TgMwB*7r3<uV`wH<IQeKT42_4lb-YKsT)c~rjf(NXM?CE zR$kOHy9;CGjuxGjZiY2gUfQKKz6VQd0|_<nuSj+_go%wcUKgS^$1b6MNMs*vNoH1k zW0WNG_EhXZ@#(A7cb5J)OKn|b%P&~3f;sTQ{p>ZqD(tnF<auEZtLm?Ak!grnm(Ua~ zKy8j&I1FR#ePeKI(Ho=0q9W5MDvWU)=7N=;cV>cl2*EtIReGG*R_(2^y2keqbHg=J ztl^rl2CM@G)}b8c!T-iM0fR9--x|kLVIE;6)<g_sI9O(H-&NssW~kEr<WRMj=EoYJ zBN`wF2I~+AV^CopzV9zfV}&R0z?r(`jbTdf+fy;kMJ7=KB{uHcL54n<2erb*gj(f! ze6ZU4s8n6x9{%^iQt#`piuW~yZ`{#vO#~~{Ps0k1XEDGy(Qk|rs6{5x=gS;y8DNZr zGCK!qxzkx{rJK=hI2mjFlr8E43Doz&I};irHZa0CSmE(37~?J)XXhJ(n|+{<dKhCR z*o8REqYK9BE_XU3Sw+-d2Kub3^;a6K3zU;VbE&&5g_}{KemcnTh#ux~74$)UYZBvE zd^(iA)W%J-^t_WUj6+xHX5a}nMXmNZ5><Cuh7s)IhY8M)kp?oP3%@bA-SXBruD{6i z`d-j02J&O&7i?rJoKN>wy6bmVd1-Beaj;r9!#~`gfx-OIT+Y2QOr8Z9N{Y<F*Oyok zg}|;0%P&|_DqKwMicVj{V4UCG`{NB-%fb^GT!klZ-^cZ77E$%i;!-)R1=yuScDa)U zFZjogum9;<sCWN|J~x3r>p=!;v3Usf-8tW8Sc`4oV_;iOf4cA24+=mAcs8=BeQ%8u z8jH*#T8htJK7c+eIp_Q9SO4Z~SVOEp_bDCNwHU17=67d<=wO_`{62}{_aC!D(742l zVP15!6a70ny8<w5n45M^31ZkRfp(%e1EC6oaD>Mnd^Ctze~dHu=YRN9?s5vNa5Kg# zTu(89pKw$Wbr)7&R9{l#r?Ry6(%z+DCrjRkNGz<sx|suF2UGKnrC4*+%0G-kds}P~ zfPt<sh$Aep-Wx$@M?vo{U^_7t&gPk5|9Rj?rQk<3;LEkO0sCv~g7#Fuzp}Fw{2&i} zGP5yqbqvHOm*yLbH9sV93AZG%vVKV5z*=w9VZ~=cup-k4NziBdJBxrX5Ua>w*VHn5 zJ8HS3CHSc+73M$%KcQA%RHcHiQNc&3b(f{7??bjz>%+vT5PPVNQ7frUu}jDhLwi0X zEqKt9!YI=m$Bn%;N??arnp0$UjSBikHbX^r2Xml8ETTdzp;o#YQz5R-LOdbY_#7hF z`tP5Gn1KA5S|2J-#p6okYHCyTa%%I9MO2WT3NbRjC53?p{E`d&su27NY%-JzYX$Mv z`#Z$!H!z22n1d6<np5B#+As$-n1dS3K^x|93g+MlbBKaD6o4Lv8>3di9Qdit@jMWR z*~;M=i}LmE(SyAufW1+{@2JIRg8LvRXqDQyb3iPkE3t9=OTD2F>#s;)?}KD9&@+2O z_=dv}U%NpMFo(s|5Aj@SEy+xb;7=Hwr@+T45F@EYr^6bG&s^aLec<Chreav7>nZSU zLlkQmK-Uaa7uD#%rm*%)kR;(i@e*P<4?Ls1@wy<jDP}2JCo9kg_}gvl&B@yv;2B*I z15qp=fM-accu{KSfO3K;tdl#eQv&Gv9_acW#GyopiA2az;x!jlLH;f16oY5<G)AnY zHbt%MfoE)nT!CUdR&XMNq0r!VA}vNn!#YJ=0Da&w0rcPlxsQs+d}nhMvr*iqg1u59 zcU(m=uRe4e2L5ms?3DuXp9=PR7sh6W=g?v}EtedCI6elthB;h+1G?ZXId>5&vGGs` zd!72Tz3Px5zny@2V2~R)K-VwchwK1*-82c$&;Xml8Y9=@>xbuw6X_g~Yo0-jrGl;} z;Tc+xH!+B_3nAyc`C+e+GpLY*sgOS_z@C=Y_#MD%1NNyv?ihny4EDN})DR}h1JA+7 zMt=%Vq~q%Z&p8J1ZwimWCecs9?>O)|SbG}5I!!}v1)n<yK4&%y>tqRjfr0!B;2HO7 z1NMT?$;^OX8^c`iHT(Gh^b9=4r*nfoUV*M*4mT#ip0tsk!Kb-kow6ZDgU{JQ?zDIf zx?Y6V9`tYsa@Q#2Kk#e98^AP+>Vjl`9U6aKc;X&S7y4;hkVhs^PKG>C0QSW9&cYwz zteubxIzZQ7${lP7U{7GP20T@sy3aumRIsHf$c_4Fz3L#x{?&o<E#xV7Sf@<TH5GI{ z3v+NN02_X8n1q4PDS)n(VU6f2J&&RIdKUb8h8CODa$syOc+Ou9jel0CpFvwE{d5B8 zx(~P4Q!!njYYD(fSkdWltn7jXhGZ|laBdUC*AMgdIt=+)nYLaUe{+2F541TvV1PMX z0bRo!06&>TJqCMSgmM+ilQ0Kvu-9~W4lTaA8AMfkX)%Fc{N1sC{3EVw{q)tK>*{%X zokJK6{F?opr7tV!IsojIjN&Wcj7Erm8$sVcZvP*z_&On<(@)cZ_%RCk8qHxE#=rZ{ zBJd`}*U3^FSHKST6X4f|XigP?Xa1Q3pM%~#W*l45>=nfgh_6==-h~|DNiDT?jsbgR z1;6-bhW`0o6q`T~OCdHrhZqj_3bE;Q7!_~><RPDYSSP@Po>(zp+kfRK0DA=;BWxrJ zK377EO_2y2RYM-y2Kf#vvGo5}hW>fa8~s$ke~Iv%o1$>N1wNMsxl|ls$fEfi@$<|7 z;){Y4^VrNNkp=9PwFvNLfzhphXfz5Gze<^?F}=Rx(td<ye1AB=YXc-866f}wMG zFE=)PiW{4f6~v~N3SzSu0Yl@UlZyWHhO-dP9x7h%3BkbfaH2X8KLE@9BRGuq<^O;F zLM()M<wgfK%mEl%5d1_Ed{G7bP#^NR75KUv#1uckBSC-*F9UA73^^nSV!Pk_D-v#y z3#}mE>I1eq06AR}u#X_rha7-S=%L2@%Sj>306B}k_{`;X;6p~>r!gQyVX2*C2l&x2 z#G7fTHz+8kLH+_v2X%|LI>hEfR9gH8To)umg&YHUY#YLMkdHS^Lp~mc+Oh+%(HnU7 zjSmT&CQ#>Y1dPW37zvjJkDn&dToC7oAV&w-{~Y+(4?hMx3AqjGK2*1%+7EIA4F`B> z&f`iIDy#t&)?gl6N>BlF0QM81HX?j^9k5x<vU%*pJqP&gBjBe?P-`y&9Ej?541B@> zVmY}Od>qz*7DueSsjvp~c=J3hzdE0$<#2a>SOZ;H1ISN4hv%`a0<3{7U{A>9A!rRY zQE8ZD4d5OD8a|9$NW-8>EEHG+E06=8eFN1uu2AdJ;uxxr&IZooT_VIXz|?jQQ-Irh zU=8ZQ4&H(tJO?|-1Utw8JIDY#$b>a`4tV7)*ui_SgC4-YlVAscvDd>I0Jg(1FdyJY zZiG=04i0KbWTV9hqeK%t2GMXN!m5CW17^Vv-a+nkhn#Q#>_7zUU<ud(C*TVf$e)am zqZt4PFu)q1`jrK+G2kVrQLgM120KuKHE@PCC;<FAgX4UJFYz_tL^9X`-vAgk8R1xI zh*85hRyIX(FcdI<P&VYGB}EVmORaqV(r*9WhYbjOMy|mc!i9Ok4sIcy1jo?8m4Giy zfGsLQO@rbiip7gSj&hW9@mP;)4)f5<#ejiOo%63Au>KgVKgh7r1>s-3PN3Oh1IVx( z_!4;bZLIKA43gn84Oie;8|9mfcW16J7Mowi9}hNw0Y+jdvp>HaaLf+C7>a;n)Bty@ zfgY4V50ceBssh08FoIv=_aOXT<*!Hwxbg=0Bf`h92DmN8v?eptK|O(g4{#aeqT33v z7T;0MrmaCl3&<dd<9!s9&Uw>AOu7JaG=o15fh|sgEt0_&$zY39V2i_mIa}e`-ZlQp zOkk5Z)~pZNz69i`{>>i+CqNH}K@JRTa{l>H3+UhF1Q`H>A{>p@;5r#%!U-Jv*tmlY z5E%hG+{3Y`{SSXM{|kRS@)DlC0O2|C12`oQL#;OPi$4~F9u|U6;Lk>X3Qq#xL#q`| zgC1t_oR9p`H1a;op9SPV7!uVCwt%;hKjJkg;Nf{3W{B!gDy#vDi(UxtfiD8S-^&R8 zm<Te=*Q4N%;T!BBKK`sBeh2}01K}nCkfRRA;iz6Qi5dVIWZ*<N8BXFxs3|&VYXEij z1uH7p0j$9(TCMGQtQTb14m=+Qz6hs+IMnoCfFq;T+*dcZfK9GNcKOExuQk9IKn_>1 zNh)m(pmsIA9u0Ak0b&AHY!OHYas<=X-~z2Ca71fhhSz718*V@?&Il*=&fW=wdOQ&1 zn9mJp4Ymb@L!L%<`Bw*y?ex>u0{;3yTLau5dtv^PAP1U1I>mqoXa`&TnHvxn0J)(D zIMSVvqcGqC;5;k}xV{5wtRK1IBj{lh#HPP`H2xXR+ZiSzJ$Qm1U=4t~L$+uV6<cf; zj{9P%-D#u?C&&$RwAv2YBH(dnbN@=BE<LOPR_SI;54=q<o*VG`2<3)7mw{uXLmcPd zJMcO{KYb18p^;Y0;Pn#N!F2_cEAch3@>&dQ@M1nUfF0Nr0Y@wVa$u;wukt)533l)W z{1I|A<VXMg9iRtMu!q0*=$~s)sE=cKdsu@x+#hjUjD84u;H2rn1FeAstU)8#Vkguj z@(`O~-bTPV7_-6}+@j@2RI8)>c)%+V^bR@de|$dr!u%R6gEc4tfBa#KQ}Aq)w<fW` zX<f#sxft@vEntf~N*!$Jpr!!s%m@QJ&;~mgqxmD^p44kW20=Lgp=Z;c|FhmZ0k$~# z!yh3oHozJPqctcx6Z*UH(Aq;>76dye|HU6?Ko9013s!Ye{deF0pWe{c0M)c<_n{7% zw?*6^Qy?C(qqz9vKy?n-A`8@Q5j0!;>5tDK7C|k2?8jIC;p;CouQaT|mp6u}uEzZl z^bmQz=xi`Os(Zl(u(l%5#d#+akYk*N)o_382R-aVTtubE$$$8{zkU&|!5h77I#>e_ z+!lZMqv`d2Sc7Aw76EiMUOI@UI1F-h|G^)F0HZK~J^VW4Km8il!-;f0u*KIjTg3el zVYRP-ncV@aT>x(K6ZpdqtcG$!@ehCeryux-8-Uk9F&V`qIamYG1LQ}X%fdN73dCg! zU^T>DQXw}0UUljRJ*XgmEO$8nhtdAExA1JNP=kczu!c35`k5P0-Ho_YR2N$!ewIiE zJ-C-$u%-t&{<Vkwjk{2ehF$<0tU)yRBZX#*um%XLq1qqux95CrLtNxSG4j80z_S4- zr1HZWWYTg2@<+g{&})O3^a5(9m7oi(1kV4)QJ@bsJ@5vQp9NqI?gCaLLrj_jU&w~q zX)WR%0W<zL9{qDn;Dmo*cHqzx^&vkRLp)pzJu<AoEb8a?{;#~C!ZC$mf+P%M@B*sf zcPEuf=S`)0lR8=GNF)XZbaomsv!HWWh?#qMD+l+~QdT&Z3eIwDCCqYdB~Vy8i4+DB zkxECRQZX+o)r*cwB{5K`o#<qtQir+Wfr3=(EP;v+)GtE4A|p6|@)Ya-_wy7W?*Fr+ z(n|{~CLY8pJ&)5tE@K3I%L=tOJJh}$fb%(_-rxkB#|apR6KZh8*|P!GV*$L$2z`3G zx)gS-J`An&f9EK-b;XM8Z4p1q2swxs;_rIM0eb)sXaY7p3w4qw<iRkg)sg`V+=ra} z3@|bY@Yx%vm)-(CDgumF1o-SN^sf+?o(DawXV7=J4|RMp)GlFAKY2nec?N1}P2e>4 zKn=AXdMUh6!!dSZ!&q}NG$a3s1Nd01<ZK`VidhghRUl@1K<rM3zD^m`H=oKJ&W}QU zLB@F(ocDkl!_AP!|9BpouXQv~{~B@hG#&`BHtbE9=Ru(6xB^@Z;)AYk20jL_eV_(i zONQEO6zadvz{`|1$MR(WCqM*V;xP2*grEn=2;9KmoC(yV7{q)Q$UFOhtBHlU-we5D z63>6AKM3_1jsu_%iSyV9o8$EX^dnLK1@$CRt%f)(oI?VPOv9)!2Sw;*09MDjq}@1| zgZF+yx6=3=*r$N=DNsk_wLEYtc;63ti%`!`f&8r>5*H)^w{i&h9d^X;wBG!U--J2> zgA?)j;ZReJ;CTzro&G3yq5KJ)6XbAI+d%INYW;cc3u-K+1H^wJ9U%S*=>YXsfrEm& zAJ+liV?{c^Yc5$DABJjA#77}c4E0=5pA2ykhz~)$6yhUd`DnaMJQwgX?9`SdmNAe& zrS&Gg0OH8dfp{v&b#RSwgm_PZoI~SjaIWYQ<bOXJ2ElO<(gF00fV;!{M$nt0@poqP z^|}Gr5z+y0Xt)l5OT=}6Y89jd=vm=908SD3H0bex9U&bcI}(980H+nXW?ly?=5?@` z)_+Yv+|mNXZ&5+^(2i7$4(5UHA$V()$O!Xzfb$!Ww-Kigd6R};07K!tBkZ}M=>YL= zNC&9S2L6#&<IUImz=PsCI6kj~qckoO_)wY-_Rs5J&%6$H&ez{t5H~un1H?@t9YFsZ z*8%WSh#SNCIiv$tm;+M*%z*=Rf%@UV)g>_j?#aY?GQ`y(9U$z1>i{@Iq=R!8Y1}E! zU4jnm9H5_LI|#9;6?9Mnwfb|YPcnc<O#mH)gARg02Z2y01%eKOfjbEY9mE57kpVh* z2E6V&&_N6M@&NcU`S-pII>5O_&;i~%1|7ul0>_E-dmukcA<TgT`2om|^?9E}d}9>C zuV^m_!cM@i;5xv2{J0JPM_PFe0+xLZ@zfV|paXs&0XkR-d<!?!SF8|E87o~)=_;K} zuu7jZKVwC$lNMGRv=6KGJwONffC2n~74iW$@E<E7AM5}wN)!Bm2!8Mk`nccz?8~47 zv`+x(0PPik{RpioOibVpfO&u?ggF5IwxZ!*oEM;Rwzv)egPskXgLs+=I@kmC0DGmi z3-DH+Kjr&(U(|+)V}r3=SVP#R1t7m9a7jrJPiJU4K)#In_sEx#4sc)gX^!W@S`uLc z3LJnL(Yfc1VG{6sP|M*Q*L50(Mx5)J;0Bn3Ae>mqnalsKxPSM4;6kwahz$#&Pl5R1 zzlx{RFb8$S(f&B*xmrG$LnW>Q+>W3(X9{%)&6i0q2X=_7KW_Q2U4d5yOtFa`ba02( zXZTY*ZG)V!8qS|GAg=e#P71>C*JyPd)RJg_2<$<b_vLDcM~fl1{k-SD^#V9yup==x zh^Mb;-0-iy40~B_@TG$6j1ZH4itq;NAAAl5w-13XpqCH)*zbM058}}_REPZWC<p$! z-0lJ$#M6b)=idVHR1xy>aqwkx$jjD{m#rZ$pMkur2k}T5;?Y*v=dl?0EBe3c%|jeQ z`(uP)4sD=|`Cbpo%P5{MCqrH~0Dr(>9={WXR>R%lgL#yJ9Z?~!{^0nKFH;~VoI#v7 z*wfGZ08;?AbLE7b<_z()4C3iGz($i0Pv`KwjQH2zu+bcFK9i7_`vDtOf$aW(?-nCI z{KwG{3APg|j!q4j!}KrMXg;0_G(%1hf_eP7<>wXEfNvod!90jyM>JmOr!PZ1H4DFA zWFE$X=J4Y{91Wb35Kjl8r#GL|pvRBrG{n{XJ#2Jn3g&R|R~;Z;y()k}2fTVD%;B%F zQFvQC4;|o(A0qtj3N<vUqqhOy--~qcTRg@00DS>oX$#Eb=RJsfFSYSl2Xp8Gd^*p+ zBWz@a_N36Tk?TA*@|ed)UO%zXAYkmBzw&>O&oDRzAtyA^@F~JZ2%qBEXr%G_Y6a+( z{Cxf&U(osmX=o3^ZCnR9CxhbY4_}UO#W}+HIY2JJ-dPjT{)&gc;ZuZ-kPcwq4vtTM z!$vfG`V$+y1v}yajE9SX_Sn<~OTk`KX;YZP?C<d@!bTgOLOkUFjQ2;ef4(n2T{6!p z-8}?$G=_NQ-^bI)a_|LyIPrVlSs~sX=X_6rE@uCPPf-s5^&xP4`X_AUQjPWt&GVmt zDPSKD0o7?QU=C0tNdCk|;L9_R*G{7T1j-M8@4z`J{WLD%q+WtAQ-2>%qh<hGKu<Rk zY6aRH5GMqCvIIaEwP+8@-^QoNmk~aNKDVcC7u1@ffU|K?YJ(Jh^i|Nltx>$^jqoX6 z=Kwyvy15i&Ujp;^d!pn2R;UMimT0`*NwA}7nht)Ar`KPB?A%3Wk@)vZti7;z;Kwiz z7o6wA`@(+@pW=1S)f#UNdYA`Iet#vb)=!xQu+d$-&I$S*KBWN0wyD3e1A8B~`G1rj zeF4|OiS)%VkGJ5<po8DVQ`id^sYUaHUvyyYB?LNn557#(0p3f4y=AyBLy!NrTJsZN z?5&U&pzb%uP)`%$D4Z$^XwNmRuY+n$R3o5TQ?d@?(JDCqA=zp2|I~pg=z#p^czXRg zU<+<!N9e$HU=4jhYoZPK1Nec<&sr058ZDmA*P5``&%f4N3-(?b;`f3Nqdj>nFpt|X zk3ZI$6!2vy)H8&6;NSm8|IGJFmVhpbz?X3y{D`Me=b-(F$ML$;^oI_-&^-7+2PB#f z{wkiD*FsKM4JS=@@1sa|*z>2Z2yti_asd@M7ua`!YE8V}e-E665S;(0*gwBVlONyf zehTbp=Fjmo3ijwm@*z7yM~SsJoOmzv5a?j=FXAc6%SOOKV-S<@dyxMEuHXP%kp*$+ zkF_QR*8Kv^2ZNY|fB&ETGp~b1paT-FgWtzfGRS_e$Rq}@pV8RJj)1RVgn0x)Jf$Ez z`la4aguKiKc>x!r3i^p4JL>7_!aOF>{#W3=@Vz(lwPqFgvH;Y~|E$<QzyFI4bU_Cb ze~PCjQS~r~bw6}~Zk&&&OF#!te(~jE;EmQmy@r0HfRB&+ulvcRP;0)0IP{}O4mv=* zkqWuSU+HW$^y>f7-r!H6|L|g72b`dThv3W9zlf(HfGHS$`tmu@feoJ1)`JeRK?fw@ zjdnn8gMAhb_~=k4WA~NoQD4&q<}nBR9B8}|t^?FtJ)95m=y%xe4`ThpZ|CKQn3}#H z<}mmpp5psu@g8(E`VGi_q=TZ<G+%ya<B64712(mB=LQ`td}ka0eK^{_@(L$&=!KfX zX|@CA(e(>H{lVRhgB@w3zT~gi=pPpNFTO~dLk1IEqYL$s4*rNwufL}GGA*9|#SlL~ z0$hicIa+d5IG;)X8~8LGe3=b!_Ro9%6EEiVunBa~_Pcm$d;{%^LHlJri_EXm|G*W0 z^-QP_py#O%K45hg<}vjb++8pDv<&j=YOjC3H~Ux5{P7tszaHAhk?sJ#Jo}q3|G=kF z-vCo6<9755#{O|XvVG*gP^aw#eA<EVDZY0d_ITj?*J1x1V55K<@1uY#{<E_GkYQd2 z&~r`8`^}eOAMVe3Ke7zge;v{RaKS%r!%9<*qCPa7&PySlzW6hDhiU|)O7LYt;J<#} z_n&@IcoN|XJf|H19Sq|AdxVY9-YQfh;M`qAI@D;u1^-s(%wueJ$Z6MM9u&|8jX(Vj z8;w`En&_1~pT_rE{HNvqbv&95G8kbVS7<u;;mbd{JJffKf;yQ8aq_@l!=7c|1;E|; zLR_83vC$uEO{X(S;L8gtTu%S`sQ=pQc{^GKIw<|kmw#99lR@@dz_*~^glz!sE&{l_ zNyu&Udq01NjhdjRFABN&zb5Y=2d2r7_x_JSJRST!HbVR<&fQT!{%+`ne8l(8qCPy@ z3-G&Mi2XRkB%=~LM~F$*e|*e;^*z3?Ob^-7U4PI4>?itBYvS<~=YVnU4)yR+FT}$C zuX-V{pa1;7yr2KyogmGZ)6gD}oZsT<5AJTBho|wU_&$W+^+Igi-a$MPg7e=M<6pj? z_B$eEM|Zb_4x0arjehHepx+&%{Ur0fkZ!Oe1>i0a#`u??|DU+A@Fe`MS0C|SsYfB6 z_WhyOMBLqcF9he|Q7>c^eA%p;sE_0A|AY+x?ih%>Bk5(p?}G1~179Aa@u$D=@OUo- z?R~yH4ZchS9WcW@{@us^NACyO!H!ay!4I542QUZ7%QSpCuY<5@;NLGp4}l%+)BE3e z;Con3-hnyXv;^L$m(~lxxw|3AX&w+y+5d{e{@*kgydPz7n;zts1s%kK4w3<%9w;`8 zU_#jL|H?t@YbMgemEnzH0#;<4_<u$8|EF@|n}w(tM%z#XS1;Hv1n*(*66{{WuugPk zf#0U1E41wKD{MXRLhpaMk}#G|^qvG;kvh@$1YtYUFW1ib_nm@XbW<>1Cyb4*_;1~R zTw$xp&)>eYc+t_mL%Ys0kmyJR_$@pGi2+?Hbe#-dMEuHvu2gg<^_Q!c^C~nCs+X)B zekIAdGobe*!72RO$vwq_-VbvR)2=L?-01xj1Bi~UbYA#%7Q+bWJq5#vzh2R6{6rhE z&}+0A>(?vVl!e}-jaleA1uKBA6e8_Pg#*I^)m4=_*_NY?DV!<?_8-L;6&8|}89vds zy9<a`@3YE&1uf?f1MyCArxhPg=1FF|M46Q+Fop%2E?L6N^TzAqMFS>=Y_E0RNA@$V z>tFc!n$O*=%Xj%z+$y@XGaKD9%h@&^3ZYlxNV-<|CEk@PmG)9?Qo3r#Xu17qM>P+* ztUWznQxogzPip5qNFVf%wMb99^UwY{%X&>~j$ZOfVy=s+T0l^+X4p3Q0MRm@_Ql2{ zHM@pxYv&mr8MvuOcW>|5{k3j7x?yd66AX^pVOO-51rDbdWQ(@?eD5IbDE2VPG}F$r zIl`d!g#H*)?+N9~6`{kP+Itp;If>nSuqG*~h9Hzw%Ep(#;F*+?bGOuFW9zHN@97<R zcf8T)Z^i_b%U3Muzf&N>DyUw6UX*@~aMHnbQ-KQ)EZ!?2mY_NHBFH_1KjDU*rtWKr zV++!BFfoxfyU(w3GPY~`t@R#XT*263GiQ>oEAfEkkv{w||GB_ajf&xr-mwdL78aZ9 z*^JiTJP{JNkz_3#m6jE{S9x7$n9<r(tZsK0w@dJN++A~grSrZnSH3NYyHooj)5*s) z`|iRscs<DPTuc^o(N$`jtytK#C5s|@i>NHQC_j{PgpXm9W>+zZ=}^E(Z<=976r&ge zdQ7s7;5M6!1<kA*?>^EKon5G6%R3hOO|QD+C`Ve#rB!=wr=)V5)Ksa)&gJSY;5h&7 z?hKQ~H$vNPm-gKxMG}XR<U2-zBc)-CHx_0T<$d!!BbOsrXC<HeRKM7LCic_dGJ^{p zmfr;HS&c5$-4E}vx?iA3Qr7O35OUks(D7-i>^Qac=w1ay1D&ab1y?q9JnOlkJ(_$c zSK~hBzZE^?rPm6rCK<opK=qd)grEY?RmF+*sz*aS7!THd*~|Ap>)F`*6_f567M}0l z%hn(AT758Q)ixWMPBGE`c-ib%<cCi}N=K+~{Ko>v)$Zrka5cvgwkQ{<ZI~4brc-5* zBi1jN49<w%C@Becu=Jus3-73-;&VD<!*{aNA_<mDXG~agZ!&&-5yQROWsA!A0S(ep z-yMr9b3gjJ>14+4l9W`2+fF&Od`?)o*czMI9`9gr%t`xIrCd(j8RkCwx|x?<3jB(u z-8I6g-U%LW7dVC9k5!hGRE4|tqPy;US!!&!)37`Hjr^+wwWPdDoO5l_c?UN2pGv;; z*!t1_s~j__c`B6(QuL<jL2-7?fs-K*Vs#}Ym0*B14sMetTQEb7u(@mI((l>>9{HE* zsTW_NnBO(#7-s5>C{s<3B&jFq(Q(kdcuju5sr6a3^R=*XK)fk2;y5ulD=~&=u}4(- z$VO>c)6-*n>cWISd1dp3y#9R4t#4ZLUD~Q71=Sp<4oSMO7!u39t?^Ad&$oV67U&ai zj+*J6%Kjj+DDdO=W1RvTlq)5Z4ar{=4v%+ooO<D5SM}{~Lh712udbk*Soy8Q(~e}n z(YgCuPc?){^?77hl+RcO$<^+p-m<N|#yji&Mbhp4*+QSI7K~dDe4QxSvsQf%XIL6( zxZF6P*w|d&b>9>nOMA}_%4~tn^~?8(6LK-bsa0JoSsSk$D_x~Mn!SMlcFa?oxnYtu zJ%GNBPid^o*gT@{q7V7rXQAwCX{N6N_ur|?KU1Z;?t?(rwaclkFVEJy-xonD_Z?H4 z60&_5v_DXC&4g?Fg;`17i5<y#@v@B8O==ZchFltp_Y(Ov`ZH{s6*Y(2k}TRAOf~Og z%*)!gJvUeDTyC^ztMBrI0*+m6o?o3-78~9aUNh%m*s?zH_NBd3Ql<CUQ_C|o{aJXC zgf%O}-FB)+`+d8fHh!Dcfd4c3rEy-0JM~(YVSCJ($L2}C3-_-U-M*|Vn_o52u<E74 z-f?-3g)i*wsu(-nwK^;nW}?3jzgX8HD0AeQnla%Z57S$@fcMSOdjxOW>SWq&t^3l* zdsjgoX2s~x^En}mEmcH-)8X@K2c_gHyKb%H>rQxFsfu@}&RP?6R~rej6^`^MvD&Q& zem^XE>(VKP&Y+lTZOIPHbUsp&ke?cToyN)s{^L20{m);{9qrxh(rd*kX`M@)xN+Uf zJizM`=VbV!>D;a?LrlYTXi3yAhFl5znb{oO3bJ;*1(W)=HtFZ)+?|{JMoM-he##WE zkWtxfdVq9cV)`DDzFyO=(C^N;_(5f6?v$&tdwC~xq()zz9o5Zn<<K!p=actyFn6kw z(R#kPDfaVNLi9}Jkf6u%1qSEl1{mab6eq@vDBm<Dl-wgeI8tHGduPDw_#hUdFy?Eo znwyjT?cp(Ph_`$-@ojrDc;i2kty_#6i?b5h>&VA+9t9J`UkD%U9bdPs%e`vCxc9)- z9s9dUJ0In4@Byjm+D5)6aCJ)gt@S$aab01x2OIf#hvZS8eJ|&(Eji>!K3?6yO7^Sd zKG0F~nDUv|kN2Ryau|D6%=wO&>8ql&!_W{a8_9I3Yl5}*O<n#zSCN&ttAk+G#`A5- zz{H`BVy>^<yO@`Ch>hxc-G1h%CDqd(K6FEUyy>2WmYCPhy7+*}bIkhphMy|Mxr(Ia z^gg5<Z|<972o2wo74gn*E_nBq-IRkoeirFta$JX}KL(Tiwf87xEUafsy5i8&wa0Re z{;e$KbsYYm#IFtsE>LCux<a~xuz(!4k5^+f|9);P)#Kegg3^j=w*AJB84A7Yw50BR zJFgcBslzH|Xl46a(nC_Ri?OidtQj&i0>zNWk$)X`_f>4fRc-+}#%lSQn_I(JgLoBB z&xKDks7v*fYYw(N{WhYfjSpd#wW~mdP2*igO_t$~X^MB2;RjEZT!KpT0&-+GxutiF zmoSIHu}cO0%3AHrQgiI7t-3udkG(CJs+nWmoU6XDMqxMhU+j{iocgBQl=;=2>MoO< zWmv6j{6%1CvH!+Iz7tgUwz1b*1ct-Yvm#FjMoFM(hQszR-HJrw6x~CJdlimZ=l5}} zkMN)C{WKsabwPh)e9XH##uQ;$DG7gy^~s9aHEa1)_kH$CFSapDh0wS)&2DKyD&4Dw zyg+wqN{h#}XyT!Ghh%rET!c?zz@p0y%(>1*BJ4W_rZn^}pr;+M=N@YI>m7VmqsRQj z-fTo}qpsujfTWQ&PaglTiVIZC0{3?+F1GNUJ9;@Whw}BBvayqIohrdf!K0XjL0V(J z^u=-V-tDc6j9U(qN(()&vK~Il^_lD@K)iCgr+cAe?W5Iqzicxsc^RF@U9mPO>yx-! z!b7$s%`WH8>hxlJv)dpv*4Es%gF|Y;6i+9w|222w@QGRG?{jmiyElIBaWfL-vKRR{ z+m@Fl=;C`dLi?fW&dP`;DZgyhJy!Xu3^EoO8z35C2Rh}oPYZF3>QUD?TOBx2Idh^z zlJQP{R^q{_Xz`h_p&`Y^X4Q%A(%*un)Za9am{Q|%jjp5@JDLIhc^nOMIOk<2TA(ML zPvTiB+@xBD`OXCyyYBN`aKW?KJ&|O7MNy8!?2D$e##gL!ho+jLk&LXxH-4~G+_zbc zVhiL-vISmU=z5}Z+Lv^<B{s{DVe3iRpaO-($r7i;q~9qnRw^>o+j!qnS!Q-coV*_| zDXq|Gk}bgbHj>~fqg-;S$^rKWiCxVhqEn&MLEm;;8rjDfEimb@o_yFT$H=27)UWF+ zEEFuTZdg`bb#9GPVKeha#jbeL+Z~n9@^TH66v5|~)U*yJ-1pw$T~C+yNnB(F^}2L_ z>myxtTi*>HN2B8#FMszB%4K^b<*I!1wGywQLX)_KvYqgn%`zc*wh2Do!TZ}8;-o$W zllwO1FHx^}v!oiL&RXxPv`4QwGwa2sC|QXEQ|#dSU5~O`#9bmN*LtEeqYV_D)w^^_ z`$#v~RwO;zRp6%Gq;I_cdv3FFvhjZ7qS3lttbTKc*Bdo}K&6lNb!C&PykCl~7>o3^ z2sp@o#@eaM+$%F}d;3EFbr0VzwkTYldSQ`b*tzK=o9^*mznr^*@1FhirbB0UuLyU% zX7y$hvuE<KnJ<fL*ONM%rX7(_%G91YMYGvxb_r?Uv3M*}?DhS;VfoJdz4S+1D5EL_ zg}zLGI|=pjOs#15IkQEHnXa<|+d*-O5;-r;S9GeD&D_{~B&LV3WNGPrqKPFjGkma$ zO;^ml4<WH9cL`C6&YAbTthA*#yTaK_YBP%K)n!+ig})SSJ!-egFJSF^X}^H``yYPz zs@;9)_13<OC^db9aoMHmT4ZcS0r@h0Dla>svuZN5<idk)UxG`*<9!D2w$qh;5NRH8 z65Q&-q_4YS7BhFZ;@&fA=_c#F^<?C6HgUZn#SOO_hMs*M*~8c}qO?1Ou!+(<;2`9# z>O04B+Ci5S@(Q)L!%L0d^_l%Po9(UI!!er$V*P8~FMf2YEjj9Eg9T9c#W>Aeabq^C zS2p)B3*F_Np%AJb7O;r8H$Bddmu;=YvZjG3cCtqQ6uUfehvBJ}e3f!v*(Wn~D~7o} z(xWhsThH6}+~8H9o+Pu+?QkB*u(V!K`F-v+zvCUtv&T8~Qw!E_++{Rg)|(<SIhby{ za?Qzxm&dQ}S#r$KaMP+|Loxi{9(x+ac==r2ALSF56IWSw+VI6_P<qc>^_$D-#=ODn zf|*sB9ghgk6nHK;?D*-M>hmtC=b8N}%*R;Wk9U34VI`2H4VypF?Y|?^IuOu5vd{24 zkCJrUkj*~gLq7t)qlEoNEq{^2Uz40Nb;d4egC~t$;bICYO?0g*Xz`FWCS-o9Qd(dm zY8RH0t(x$<r|*9M@bgABSDvmje$*ht)Q4Gy6HUX14xHb+(9z=%Z~7JNl<NxH`~~K6 z>WM3>*MXI&#J03$uMhiV6W2Xe==-9XQQWa<8NJD4hGwUmRqU!mDy3OVr<^)t!_A#u zOY(RShRVVdl%i9Hi6xhhSzh43a*V=JIP=ou!b;-V-H%X;9XPNrg?FY&xvS|@rE5~| z>3a)6k&7gDefZ)gk>$Y^%im2taWPpUawt)jKj_iub^2Ww^>>*}BwS{`#?nJ{sU6}` z)qO_ra48<)|1e-aBU39EbMD~68@4GMRSaKWS~J(i)p?hz>8ubr?-7LO*qhw+#VMEi zrm{nZ9@U=wYF|#6PFlB%C_T)lSeYp2R<-LxzHdz95m&66k#BG^f6U#Xursu;eAIVt z)3YJY^{y#vG&FCBx{o}|UKL%<C3lYlf(-LnaM_kic5{uQ4xi69h4FL6y1iS-p}dsT z8eyM7^4oEGXj)HZdRt#v_$o}AsjK<)=$uMBp_4y<_;R$>mx#Mxo!8yBzbYD#6VAUc z2GS)~F)+AH)uEs&VdBc3rVv8mOwZ`pO73Uf<2yIUdAwAkWL5VjE;XPokCwJ_Y!uTT z?!MT!p<-V*y>FC{23yr+Yt`%Ng_Zml!$M~lS}+y_n^d_PMTt6Ze-CN=N{s>OeDo}v z@7%QuHrpR=c2>K-LVf9ky@v1C1LeM5bVfyE%_oWJ*S66+VLfy<*Zt<~W?6mb+`~d{ z88|-my0uy%c;~0Q9QCfxH>*68<PJaf1yr;b`89*4_l4L6WIqAo7jww#gN}mPb9ZA1 z8MbJ|P_2qY_6IE<&kGuPbAunfQZvrG`l(8!C*t-@V)5;L>T<^<iOe!!U|!$_thQ3N zSH&J%7a8tlaD1tz<GC`rxrI02BQxDe_kqF78V}^B3}$jGeCPH~Q<kJ`T}g}?pIpB% zO*nn~s;<$*J>+~Rcv}A9C-lO1;=Sv<HT)<q3nbHF;kMkae@xM{E!Q0zvg>r^vnEXY zKRB%)#GuM=_>x>E8W*rNZ+OZ+D{80zICsyXrB!nwdBNt_KSu1~IV~{$mPHv2d+-T8 zYc*t*ZKO1k%zaK-)h0Fe0?*tQkG-k|{r9}&o5V$lf?cIwvtM4<st^=BX*WKoCKi`A ztUoizd{~Wrq5OjU0|6UR?kc~_C_wir)uNq42W&o?3**`;7_ob(_0dip0{<w3VF+~( z>3Q90r`P(DCP8;K#~Ux{W3LiqKG!Yc3oKpLeJOc={mA7`o4SXTl2J*%JFCuD`YcNS zQekvUkIq>*WIB=Sp3ag9b~PoI^^2t_dyNTe!v~rC?C(^3b$wyc9#QkQ@$k{k7E!zK z<w8Q4v$xdGpOW2Gwz}9b<v{&0rDX%|*tb?pu%3;S3_&lRw2ic?fdwF+*3N|D3o9IF zZ&R6v==It38D?QHiGxuFugMFwAJ7ecm;TCAq0G8hh2WS=JfGmVasN`W8eURFNssK* zgLIc2O<;pA-`2bceQ<MYe^1H#KFwU>>L8&<cWetEOP}0lkxA~>9h%@zv8+j4AoDfO zu6o2gYQ;&<m|LYgNhNJPa>sg@muiV&J_u&1Mhud7Ivx48n=%{V#3TDf%&K_mb#04> z(W<SMAL1T7y)*qZXiJ_&j+>SgclWrzBbm;#IPr7evrBv9%_6c}gQygV;jr}S?a?69 z&Xrg{iz1iIIgu^od+%m6c_w_Kye_9!esz6dacAsugy3D<6{e-zL}gS6_ZN`6KRk@m z+^y3UT@^>Jj@8aK%Tm9Oor2gn%9WDU5&f$0fowmEMB<Pju~WL&B!;BRNXfCt9gBMK z$|7eyNt<nQ=KK2P?E{U@O;yqXLeti}{mFF70-lt8<E^PWnEZ<>BHPvlv*BC(#w=WV z$V(>}beG<%>I@)BYJPFpd_Q*A{iKWstpmravw5diFbi%DF!25)-kq2oK-wMr=2DWg z2dhZXRo?v@R}E=N8ZrYBB$V>{(n6&(8#?j`#ssmi#g4OcOP-x6jk9o^;#`_W(d*Ll zGvm>TljmB$IOouv0|La0E?Mc}`?AtR>*OvB?#p<!&o)vQd-!R{!Kh6?mr%6sLdf*N zyAu69*Y8(|H}{O(R3vq4_Gsmmq}Dr(Ylpk?PVZhr;9fPVeQhXF^VxbghHKqAgNr}* zW+ci>-L=ToI+F2<9^+zcXJ3`P`B_eT#U!`?oIqcqG-X{!h;6E{lTO7|?-a`|iBsE4 z6-k1J3O(tH6P?bkmxz%4?7maA%RV5UTCJx&di$W>Z9e(9kzFg7##hx<`R;bARz5uV zW_;DC-!^0LN>N!h{hi%+yrrw{v#N&;mfW?K%v#q!b!cjp?d)@7!p#RBd0xv+Him3x z_Vv(^)0R|Azoo51_a-FolXqJZx5|c9eKt=`_8hoJbk6C3K(5*2utysfH1@$Wixm0} zM(doNM{dVlont2}R$UodUbeaF(K)Gth{8(Z8FKpcQNH_+*t~@6C!LeB#iuE0#d`hw z<mNWM|2#SupVd*ZKY^-!G1q<i&922;w4Fsia!ouc`Fv#7DY3v)>QVt`nG$!7hVjfs z7|XQYJ?)~73KNgbcs%nx8;|O9xdelZkIGyYgk9eVT;(ylQ!S6zyPw^7#q)Bi_X?hq zu`L5V$F05Nosx^TmvWYE=s2=PbF-WutC!lPcW&ulRQMhr6vd2)DQk1dt0s1od#ueN ze-4=r%AJX2<3AE#H{O!Itk(L)Y{nwKt{ZF}BVV+x^4%lTpReTJ`r&bbCsS0~#ZM91 z8-xWWM~-u6>PQ-XIFb=Z9}U1@r$$kSvtg5X^7VlUvC2v6lHs{@m!o_h-L-+48Kvt< znz`=O*8F59mWy8sJU#tZNCh2Mms0OC(H_0k_PA68OW|DY=2K-H)iKa>Vb<*I?RFkx zpJ@uWu>P(K>q{p3#5@Kb1%~?U*P#34(lo+PJ#qCyjHtDt8{K;>nEno>r97a+KvM04 zN=5`O4MWW|rSN32;h>&P=mnLPM98F*j)||HlpLojvNo#1M9Jwc+l3EAv(M7IAG+I{ zW|JfM%+tGk27LVNNwF20+v1*K{pkfgT+chZjJ30GoqSv>>;=(qt=A&&`)yU<7syZ8 zm$~R(kB!v{EcE;`dTzU?nyzv0P;Dc{qp?xqMbnr##0tsJQ`vbdB4xa8%6zOIPM6OJ zrpFu^<=8?#Rq;=Z`hV}wG9*|}PRFz9B9Ttlb1XBJFzjOX<MT5nSc)hK^+$O=c{gI- z-<I|5T$1fneg-SsvGt2dkb)kPIA&H%><}PY`y2WM?!W9Mro+muTM?eGw9DXyrFg-) zh{RptLy4h%Q75Rk3_SHe?Y3RoE6Te>fBj-?c(r>2!L!;H3UQNMJA>ZDblbk*W}m*p z)cn2K;?3iVDU8Yd_FeMty2s2foQg1#+vTw%L~{wh;<HQiCs|C3^7-XtlPwZBB<_!~ z83F!zv0vBiN`>E?ezD<f{Plpjl-=U1%O4~YMcP>0i#f<56Wa_Yt{r?mO&L7SaI9ld zM!fu*8nRaR)%2Kc)$8VLP7NP*f6{oCw4RjJdOO^rU0K|~#?w^eyGWjO+xwTnuNw*0 zsk&X;`6^dzT1M=te5R(gj3rpTtKxEIn@6?&PP_8vNgF1+08tsmi{i^d!?$f~pfopM zE=m^q@~C`d_Jq0Q?t|;4P1os6aEl7egxfpK?yHwwCal-YODZCn3Ut}LAK|axe10~} zWzN2Iou#=qo8d~?0s@gU<z|(QZdWQvw<_U$ho^bPGaGgN<neLwBkp>UV!TWAEHB80 z5cW;I_N1#5v6Q0BQhdw9mWEENzAKFvcw8&$)igt7&Z@fTwAxMO`PQy=B9~I*YF3aU zA(G79wn!+CJD(FLV?tTiNzi&OC&y!QjZJxNYoKj=OloV6b^Xw?I_|~Oyw#Igf{q@N z5rOGGSnvxanU!qH0uD86Z)jHpd#6O|&|xpJ;;z97hULuzEXM(U^o-7J?iG~!a_FZ1 zq@Zp^@bS9Af&TiCLN6O1_oYj}PVH(?oT9fK<yTF}v#K9*8(sf0H_$e+pu?vjZ&L?h zn%Fh+N##atGONtqET4h}EGNg_4TGQXJU%Ll8^8A=uOs)lnYROhL{eZSuKn;e=h;Ee zID(aD4_nuPt9@P-?7qfJ(^*#A751n-w<#9wSSBA4z+<g(Y9sN*(_QY{y@T_7?MyUd z4@7L{4c^@*+HW99V7{nxkO}K&pIz}-q(6XPaZI%J-pW?p^=m5=cjo!@lIqH|j_qp8 zUz%qrzjQ+3vsfa*EZ<Q(mt0}^!R&d#xl7+S+-piBK9BUgqqLrk@fS1@zW99kVC><y zh~Qka)+GINv3&KxR1Bd8rHG-tJ1e+KPxh_UyqQCewp1E@>7CVK!($z{`{j4B*IXJk zhvXaRi;sTs&9`+HbxX6Y_?o)e**sF5djGQ1v=3WU%B!zm$Sh7|7Xf10wKA8vit|rA z7%LM(LWO49MP0V>JgybNI@{h%=JTKKNt<iEXLE+qs}@=>+&aLL$)I~Arfxh(blG{z z<LM!tnlkx@I>z?hBj;@nNVKUJx_KW`m9fhIpgqCRUCc%}(B4x1S!{)F#=YE;o$7lB z&9g72d{PLJ?K~73azBE9^PR|pbmaqs$9>KeR!u$1N+gO)N3@@?S(o%IhrI5>V$YO? z1J)Tqu!cmLPBtTZ8wZ9HOE*0t0C(M-n()F%U)y6k^s>|Oy9JGl=gbSv$hh<CN)&ir z+Soye;xUz?+#<~!O7G>a?wxrS>8~TogbA>{>Tq2M$+Owe@62kgiPcQbbW<PROP{=) zeyU))t|yaQ|8~ktv#rPV58Q~{(?IBsuT3imWKEQr4rYq1;O9N*WovnAZbi#LqjIre z{zJh4yHb`do(`|mr>ZUV&f-DsN+8#?Kuu<g$HI-r7Ztlp%lV#o?Bka4v9SE>s4`Ey z(=%d2qrU(#rvA;b70MC^yG%9OGSfZV?7AM5X>~LE7H?)c_u1&P6M5O3WzmRv^7Bp7 z!a6B#cWsYpyPuJ~FTsXMaWqL!7vA{H8{G*6@biWZ>7&A1&A;_O=+@q*(4`(BBKjeQ z%`<uH(hx#U=Jl<;N<r>lKiBb})6nj)etjm&T;BT4GXsvehYs$H9uFxhG80Qv?N2Q& zs2R%pTyrG-<36#ox7D$cirhAN<v?>8seH0ydDLp78l8@e)YkXo=d|6Ur9CKe!u)Gq zxcVR7{!C!E;x0dmig{tdRu#o9F`h+pktZ|n7u*+}ZC#%DIBt)`+LbznNBRu63Z`^y z=Skj^`Lt20T=xChcVb>r-IJQ6373HQPu1+ZDOvZ4T+F8<3g%vY8rrgMLQ7YEM{1Sf zi7r{Ax|Cjj#SO$D-+Y6;EBCsGS@1cuFv;Xyv|C%S!s}s+8fU82>H1L<YkAk334K$V z6^mah2Re!hJfW@+)mgsAiiy*ON#5a2W7vL|7Ws#xT!ziBTdMAd7hV3eSFS&YXR0AW z`edd~zE@0LkCUi?&!bGqe8{>ATDJ~#nVBg>8k63iHE$)P1?3hwL=}i1DsJgLV687X zEGu%r!0=Sg%ayiMo>WTj)$7Tt=e|QB>Ay&t^3;0FQP8GVtwhJLBiBAPekf!3Cfo7z zLN!l&x;DHD?byB9d2WsK;#(yQmSqkzBd1AGcRVQ+&Wzsv0sg^Re>PouqMFGxlq;p& z>)XEFI7YM&9`M<>W4*<|uB<Q<mV%mvD<yZCo;@Ij8R>S0H!K3oB66eZ(+h=S!Bwj@ zGPRvoJ@fn)XR&2G4s>;gttaW8N#^m<2Esu2SN`*pMJ*wO6M-MLx)37LG=Xfqc4T{c zD~r3#fZ^D+?hwMeSJXlKx)F2tRhz_yu#o1VN6G>HO3AA;uv2|R#w>-i@UUl=uh$eC zsx(#Ib-V0nOXQP{=JT69E&o*GcBXNj($l)#-CVw(!h~C!tYh8sE6Z-m@Qsh$De$z8 zhpPGbUY9wg6yezwyB#!0SG6&cDk=X#^^0Qn_3yu#<es`TyR-mMiznNVw$vK$oIAz4 zKQq}#^#?55$S?bRMEIVcZIO!MG4+yV^!DZhvyX#Xu}$21#Kz<d=CNHa%ecrc1tl?S zqk9*34SH<xICmh$Yjqm-aR()ouKkW8-^zE^>`I0Op2uZISg6xqSVg6`STu}HufWVx z?it@o|DakL$KP5qWj{-Qmb%H<_G-jhBQ|nc>BC|oCBIU13Y$Hqy^4Ks;_{(yNxrxC z`WWZU&R)7l{9c@#a-6GFJp~@+rLw=eEVlD`o9CUkkG|~=A#iXGoo9RAKsYkPYfUcD z{Xi@jy7zT7KqT&NR@C_5XTDVlJzvc$87Dh#^t`_irG9W@LEy?1x2HE?Cx^6sPY;jv z(PO8rZA<UvbybV8k;5Wi9}>1qSk`@a(4Vz<(7xHk=i`AVr_a0-@tO*zo+a&GvhGl6 z6PJw5O788$(wDD=O&+7(CXH)~zW8wH<{a0?6o;9Hducr_-!JZZx-4B{W$Y^>;YYl- z?LC$5M)5&Y=UED<i&u7BKkfA~QEbDay`%Z#8>K?J)G)Gk4V6-TRTJBPST1QST0F7H zzt$x8aIfHtHsxexzaj0K>EjST>EcqZTGx#0ZI%t+HE18Rl`+mUzputbE-&EB4xN^- zFLyY(9*@l2P@-V=a?ZU{)@cFXV@d*#@$3!DyLSEUQF1$PJ!R(>VcH5@u0-{f?d=yS zO7R6Ixijh$q`qC39LcZsnYblh6u1f|eyS3idN@A0n_{=9_4&qp^$^E3VS&L%y_emw zZ5=V+lV!%|PUm@hc#)6Val;ku1N>4ZNnv(xGA37R#R%0)aEdU=KmM{usA_ihN&A9v z%N)TG7ROrn|1~^mB!WG((5H$8AKa9+OZy4fbJ0qofiq|SE!*!8&G%GKJQ<!mJverq z&0fp-?IVh{;mOMhhv`ENh4N;%@%Zf)-XWTqT%d6>YR}H}wz(lO&SgwIC#WWCGM#3t zv#h<&Z_g|iudd26)TeItwb4EQvI>kr`HaK+=AF`^5|jn)4o%{prwTV^(1kB@wyK|A zJe=z|x{)$HO(LePzU$F6(4!G&P@Fh&GJ~S9#sQR)TYXgn+t1?RnT5r=>9?6$zCK;x zDOYSbHYvW?^SB1JgWY|1bH$ev0T*iwLqCb{O6D}ZwPE{qmvh3Z<e5z)u*Jp_OBHaN zV6dv<oT9kMY<_>{W_4aIKc~p6N9!YY_4CWeEHS)r#P8F@tJ#OE&wKS=RmW&_nN|!> z=_I$`T&<+#qDEI1t{G<yzrH)bINWTTOO*7WF#3h_HTV`a-ibCFCro<W2}v?#D|$?s zV%j7+u!1c5K{b6h_vaAk7!Mb^LAUqh-eseQ>lGHCmT@k%<yjM%lyv&+0bwkq&Z<3} z!snHzU|hX{u}G1(`KBN?sa4~0WhWUbq7Ku?i<2g=R>f_NNbrtkQ`)rEw3^UuXh8C3 zC|>0;!LTCdhAtuY`RH~(8(~@g(FYRy4=XftIvz`W$s5jL_JCR7bq`;tu4Blln-aZz zs3_4O8K(zt<{+=H+B5ugQ@^<$(Xfeo>LuGQs4Sm6rp;^v$-%GYY@>%|!_`lr3(eA) zF0e;w`7$vmMxOLC3Ekb3Z#?iNMkw@R>(@|+2w@F#PsYC3SB`5oKm6$7B!2juQ^$?l z3YnOfhZpx9;)3a5LRrkBs827hNKt|sUYw6N<S|sOQH_o49}Q?b)Oc@oaia5LMX>_U z_Vrq`Az?`l!gd^`)&pN!xT{*uGug5X=lHKAs>I(in%ea$@U;4+<r3fh1hUDIq1@jt zk&<+<hdtEn<!+R+SMBn0$8Qsg-4)+PX-zc%=+V|se7)AzSM*e-%!<d9&D><kl^Z*F zy5HQa^t6?*+Q7R?amw1-(>LO-<y`*xS(if3=C2B;EnFs=z2(=7)LyBD!T0yIb2E+3 zJ&Zo-{xY<$DL*<Z?J2)t(A1X4)QAqLekXw}m9Z_}ftH7d<Hp6uE>5oR?@9Z#CTQ`7 zLl@V`r4+nusmK$I&;Demox5O`mm_*m)HO$Kq{GiRkLP1~RHIgRbfB(myCYMRFsB}q zt$D;KRoZVswa{2WOux75Q(LJ;0*97T(zm``)0ely_R_M6QP+EW5Hl;Dt;aRCYignD zIyV2gkB)~aL4j*F2nl$6mgauCM^Pt2Su17VKKBDPI&q7S5&0FP2c*h0vQ^)I2}qNt z+j-DOhmN)XW+@pq^z7ugdA{mt*VK;C>4KWx<qGeVKeSCkpbOQKGBEZTI~iP6wnoTE zX4uaA3Uj<$yV$dRm*_cVN;>x~@80$}($lUz<TYzf#_+kW>t`EP&WzXF$LSy4*?d-C z%H+Ge@de+Tj;}3jQ@j))L(NeF=sty;KYb!%J6l8gpvCshG`vf?lW?c~J*7}?@dSgM z&hm*uVc2K1vDR3RT5$J3*eCJne1Vj$QHBQVvtmUxUsSnd%@qfVsWCY1{Pywy#axeS z+wNn0wRL?2-};)#<QJMP<aUe`)hQ{Z-mCk#nL_fEzR%rMThL7|9-uDl9&vPXhv4_g zJMAHVbFU@40I?{~;Pzdn3ldaW%N?URM(>~Giak7PxnnNR-mzbM?4=OV3OKYwGb#}# zdJ_WDf@7D3EHmDjJHDO?d>XL7>`b)?s<;Z(xs;Dp4P|B9OsWWrFYU@o9B++&rX(Xy zmvi`a+T3}fAGdISM^9E_OXI9*a2ZoiuUWF}t7!^AkqqnEN!I<L(z9_aEyT=(-cO?L z9Y!h)(+ittMHIP~TNJtuYxoVcv`;Qv?!bmMDRLe%RvjKw0B2Dw**Cma<4exVPuX{> z1eG7Rxg1XZ6bcRF0FybZ{U)1k1QmKZ-iTPkmeHTWTBq#QA1oD>^VHkZbE2}0Z<<@% zrIMvC+w;cDIks8)gz!}T?&wW7Ia2$T*pvqy#uxc~>nADPM&%fXvNT(ll{}Zl^Jz+Q zmLNd3*&~Gkot)%3_NkeO8!wjRyri;`&&GWhaF|V-d$qOS{OPB-p^jl|LKKnn7}eDM zs9(6q7Y!k0;+Iz}jTXCXWFi8*hCWA}sU;jL@hkLH3?W#SyO$o7){0mOrnfg2HW+eQ zrP^+lV1B(x^t)>@_dvXnuWwgs$^hYu+iBA|!sYF`&F(~ONaTS3xMX9-^I*dFZcpDi zw$3L>)l7CqFQ>_);)7Xx{Z4r2-4-`hlv~bD?skl1-WO=#^EjC;v3=yj#`N9tlFB|x zbh3?YYt!8q2dd4C9~r*&$YgFs^MLuPQ8U-45B<8#I#*~~r>`5N%K34xBS!4CD&;fn zb!+HYlA><;oY!D)^9AoDe%YK&i<CqKCe!Q%Ey#|>R~|7Cqi+a?6g=VbeXHs3-c+@t z=Te_^(5O+wk(Rbe9?s=VwwaF>lw8S>wp`>myZ(^3sd`ZL#jg<y-akLlnd~^}@dfOY zFGO)!V^5YKrR1)+JJq*JXV90fr^2D}6(QeN6?%^%1+|OMOUbZqT<w0V&7Cro?Rf0L zFoU0N_#~&|u4@4ev)k_Hm6*g$f8K3owf|v^68}9ew&fCR@?AW>R@?71qX5F)Snw=l zW#fpss%?wP(_@_tB7FUmEE9^&jY}q$z+M6YB2Z6Na|trcU7C>$?=U4SoZv#)XEn)` z)Ouw&Z$Tcb;P;pl*3Vs*oL#ZLY;#XUI#;J=iy?Xc`bp6j62ed^7P{@+8_nV1`ug6s zak2i5%~AByt)meWiQ~Iqqmst*l{Y0TD<&JSdOi6VEpB;H2HZX(>d@Mq-@b$j=I}j; zG&@VB?AM;K-jzos_pfZHx7KmK8Ma<athD*r?e2K5(5Ekl<d!dH6Y}3Lv-9J)qs`>Q zEfcH{j++D=7Iqij#GEUALA6ng&w<DiF}E)*-G$En`yBlL{5o&MKmBl~DU&;dAiJnd zfO|O4#38pZmHWoDI$6`ykIF#VrFYA;ArE}GB=oeq#&MVE6y<YH1B(W#0&VZwG1<a4 zpFSb?<x7_YJybF~cNh5cA#Vf50#6LkZ2e$vF?o)gp@DmM|KlXO3qfB@9n|-Z49Hze z98)Uvo2Ha(yuQ`6p(B{b=&@kVh@4`JrvWLmvbF2T73(DiT1zIfr&he1RC8HkH``sx zCMsi`V6VZe{Q$dU{XC{g_RY20-8CJ}920B|n;5KtQ|>by7<3jtclrCa&CLV+k15F) z&d5vOS=9LWSZx3DF)=!Jjp$e6V~5ri80CCjmik0~1M$?C!hyV_hsN^wrjLBzZhc8= zwbh1xe!JP@?nfef4=BFKc|90d-_q{nBdU!#t!m$~yo=}mtL!Y;+UlBcjT8&+?gfGs zDbixWDTN}1phfZm1&Ws9Zo%Ct?k>e$LUAilybvf_+&$ssJLeaiACN2iT5IjSW}dm9 z8E9f`7=p#LF)2lwybFEUj7Rs(2b$O1d8)6TC!n~f+MRr5Lg)GvwCH1^>)yM!aNhDy z<E#(wr~BGOg&rTl_Y~s7;O&fO>2JgOB=-fCS4N{&pO>ZgG40kxJ|jXR<YkvWB$%wO zc_ddVqjh4OUPBQu!3p3NYA#k+zWVmvqj9Gs65BrXVn&AfatG{*3dp9}0Xq4whnbbL zdPpGVt!9onc{&)Op>kp<otnAl4x-cC1Qn6^iy%&eu?y;pJn#H+;SFblsg=)D%5-15 zY3jLG*jNEJ;s$(_Sr|zBW$h31LLf_a3yS8S4xRiK87R*4anW-1+Z<38DoO|IKo^$6 zRqO%~tKHmpi=GCtM`y+~iD{jl-E?DrPeuW+hIEkSPZzODbAnv(CP>HBNT#?;fT!G9 z+A!^(XR*arWG)Fi%Tmd)Uwnly77OgAFP!|dk7oI~+>I-lBso8j0F~1A2_)Pw-+e`1 z-CINmKZil9V+kh-t*CT8XP6x%c6oQ+mYz}X85UnRgciRrZKPd6HeUMs?cQ|veS-F4 zla{?^BIIvO>YZ<#Xc(f5Ao9E4)-&^ONV?j1PtnI1e(Q>sr?tc~%-|86^?Tx2MhVip zpSMVPk}cL5++=1&uXuaIk-uHgcKi$9de4fHKBL~OC#N(agqPsPDOUb&Nkzc3Eh=Fn zxlaHPe}AD^_>qezOlRTR%`;8QQA2TJJS6D-D{ze>dPIE*Y`8lPH2p#|a}D)B=QySm z=)k8uK(DO_d^$QifLX@?p7q|y(gCnoj@60y!=amZf#9f<RUgjt(W=s?`E3AuiILWF z%Vg90`mA=^sh6US(zD$P0*bFwAYXs8MHfF^+E-va;ni^LpHP{G_K0yh@fMlXa4&V= zDl6y$=k?g-!#>HD=|tG}7TT8=GEXrC#6h|1k<ewYp!&S&^Sv-cu)|-{FF=woR7BbH z!;tvSxpSD^zwVL3yYO5vB{Q0i-)dQ9*lhhFgav>zMrP8v=I7Lj(S~9`4|o`Ye`QMU z@^Wu))L@P>V0UyP<o}f^6Fdok4W8S?eK3tvp$Pee@7QCp3$318eqanBh6O=N_J$9% z-}%Wa#}du=EWwbtRa(+<eXlhj+9p8OX{8qyB0~`J`_s}i6Q<97a}Jw>W^646`Vsz7 zawC$JOP!e^fav!v-cWo{Z%gOao@N&4Jybzf86BnLk7y1>;9#NjH&Yw&6#cf<x&rq8 z29JrUnb+E(uf@*eDepMeq5Cq&Oz9c^m6g*53m`9#vO;fPZKNWb=5BH4_WM+zMG(8# z^M#H>w`P3}U}I`jx=3{WF3GqtjKST{<;jcX(*iP@$JgWG2p}I&m=%1KV@%uu%g_Bq z_JYz>_vQ`K`G6~yCT&=HXUN#GPhb;KbLEqHP3+(twMttvf<xSMw%Nj*HDB?7qv&6V zCoo7$KUYBgPEKIWeCI*QdAvbjcD?HNN-SCn&U)3!3ROsu>hhf=BSj1z4Fxv09O;$p zqe8%W=TS|)oTKeuHuPj1D~*mqlw?Fde6Avw70YmGC4r3MY-{J!iLqjMtC-k4Y8ut# zN9(+}=dlmIV?85_Qu#^+d{>q&nD0!R_E6D)zOestU$g6ySF`!}YNG`eye0dDar+uU zOrmXHHS+g6PrYzntS-G?L@~j-rp3;~C@{jQxeAV#BaT>yM5rs$q(%1|!_05qhQvcP zB+Mj4a$R&#au8!cuDg`gxIlqSkSdP7LW;wRw2U@rTTTgChC(q%3RP4=#bYyyq!s7F zDK;Xt6)sPLXSMEb?GAu^@iDLGcfg}`;>Dc;J;>5@lR>SZrGL<H(6gO>9zWTFZbpWB zzOc1f1-Vvi!(%(vxWl1Y6!4FzUB#B>#c^A%OZ>Iw_!nSfdG|T437B9;y37fs_|?Mx zmJay$e09E8!L+inSn<@8l}tzT;kD0pZ`nq1AAictJy+|s2Q<s3Padr4AMLx6?J>Jv z6U=R;JBYwR&Ld^9Ky}NMx0h&j*SALe7QPoS7JrIF`bGKSywRfVWhUmH{ls%!S)M;w zQmMD%+^Bq}sVesmo`8hTKs|>=X3Krghu#NWV-2-0(HLzOF4JEC%t~&a<BqpqyMD<X z=B1iZTs=1z^`ftkTgpEzaGV*@h5oyDnS$&Yoo&CRW#(7OnaRI5PP-N-g_b#@ID{MU zXd#Vz4kN|7=^#tZ{stDMzm>wxJ|p?y5y+0MKD1$IzSUkhLA&&g2gc18m4H<!8mGMb zQ?k!U8I}cqzhKuh)m+0VC-b&e5Dkna3dSBKHCKu>ay^N8R1P}&m9**y#CvQvwP_eF zH{}3O91&bGURtpNZf}!4CiWh!x3(w=1^}?{D?@Jes<(L^IT+7H2^^8O{nWpaMd$T- zjn&O}!*8IP_7C%A%0#H3;}iNHq4u)ZEl9g!)c5PP08Xqtfe06S(qipfbRCh4^(Xs> zquNF6j$U*lLMvY0rqD71RF&F`RB2+IEZk&3jd%FgJXQ3$`<L}-^WOKa1nHroK!;Tk zv3q}>+eCXZUQ_{@=aZ)HPqBBV!)7<DhF$?y8#x8XvS`MI52U<c(G4q6r`z)|1i_}b z^o~`QUf`Z=W$5!E@3|k&1}XNyCasnyp1Q{+0`b+2zSLNawP-TJFSp+8<K*|Bs5h7c zkzrvWf&2tlz(D9Q2e)HSt^E0`Kn)VEjOsUXesC8+2mSW6DMWf{mHx-8fioHAtIafQ zR_iEbCet%RsF_*Xup_d83O8jp7#*#<V=gj@@x-$&j5KIIw)v*Rn6W$R<d}emtTEV& zUjh&B@U8YBcS^q2EZ(X6fuRUGw<pxMYoY;1`)8AkRwPEm<GES>G&R%Z4$sV8NWDNM zia7BTcrYeD`E~cb3%w8>jMl1oVu(K<0{*+6_Th{Z#fejm^Ad2sH>e(&{yeeX7`5u3 z<Uv=L!0F++`MSk?TV^r%^zH1{Djv-hSB9P5;`_`vkp7t(V*qv+e?4+JV~+$n*w~VM zaGK)T`wj(CpWIP!n`^$+NdWK|qdyW`isR^_mmj=Rw9ZdQm6=fM++WGIpK2B^L-^#) z@@P1Qvn4Gwj)h8>*8C4b5l7V>^{)l?*liC0XQA-8ThOMT<0k}4SiR*N&Ks{ohxS;x zKHIJeC~m6&)RNM_0O9avr0p~6RxE@2vx|RB#1!w?tU#CKg7i`JIVAfing>v-ZnbZ! zcXKiX^ypYm+%Pf+u|0QU_(Ad?mRS&(24rE2e!XkMeANAi@~%MxDphd~2b1Uhs4+h9 z@+<|4&lK$2>1faZJU)k2+<RN5Gwa23{WdGQcr9Z;(FE-vxtQIkKta_64+32r9Ch@F z1LX(?3b$VwvQQ#P1v~@w*FE$B6?{xg>C_m~Wm6{yjOG4fLj<|ncMf@|bguQWxXboW zA@$nH)?}j~DfIEN)chx-YGUL=0G#}$arlk*RaTzg*Wtw~1~E!6IVC1_Qy}<1J&usN z(PuzSKyBSr+wKLMSRBFzQy#PZg=a5pfmOh|<)o=5U=tO*+#^kc5~-UCV?mrIzLO`< z@@-n1PCtKvJfGgOdOG+N;1BpCoS#Ia*p5mvglyhgnj`(QwTh;kY#l3Z@q?Unr5%ZL zFKP#Yz1opec=e3#H!f;^?_d3k<zkc&AKFbKQV>!I=V4ww3xgBA`@9hd3|#FzrHdZv z?{gN1!*Akt83VRyYy`*5-P;9m`?}vf#y{N%8{J{ST%p6GyMEZ1b8&Qj0&g5wPO5AX z(oeibjYh{Ay>%%l1i7FWRmq8+wm)=|MAplwt$r##%j;|U-%`j4IVy5tAsX#RQ+-UE zo@SroQKhZCAAd-(cYpm3l1E$Rbxp0+5k1Sc7yiBg8vc=eyYh}NXb14@aS`kDiYZ@4 zOo`g0+o!JYRvQr-<%{P@kjP=Bomif&AIGgJeUJ1X{@8vkK3pMqq%ZQB%+pj0+my2- zd*@r<SJ3m{xZW!7u8(9$Sy#90etON5P^+c-)N%`iACUq9^zc<H#Sp8nWeC0h>cuxQ zqgu3GrU$i|k%u}R?@rBrA+yr6aF{>q=V9l2ufp!<kgy)t>ywAmMRji@NNE&9s0)E} zv~nvse+#s1qsw3A>wjs{EI#%Kg>V;x1-KGLEX?@QZ}x}qVso&(0F1Y94M2^~4Bph? zO>ugDik9rryMB{-^^5z|TLJp4YPhXa__-G|4zwtcG>*m(9Ck&kH>`SGym)gMmB_w` zNmS3Ni^4Ga<OE!sY1rf-XI2L2#5(;y%wm68>7Vv6+~GSP(w6bx5uLfGn21Ucex_Dz z)v_csCt&-yd)L@-Ng%UQ-f!CYU8<4Gg7dc?TcQ&!*D|U##ynlJ6|PhH*d=Z}s}vd^ z8k36W^>a?6#iOS6mtfs?`b{fy{!_9_$~cgC<GhdyfS$FB_|Ad%alr*EpKX=bKKJss zaqt&Oqzi#a0g9v>S<zX22ywn06+gNEjC%Yp9ZzFxX=uHe{vHRm_&r6iFTg`~`+|DW zdaU{a7#;3mesuv17@Ej3(4FZ~(6AIW*Sad8@4l+a=&oJjDW!*|1~v>n<2)09rfZuj zUTH=>QF&tDCBw#N`;(aSx{HeHa0DXHs$ou)YrNFbLW~)If!ZI<QjU-C>3*UWM!e67 z>hbAb*7QR#4?8uMgZ=C(Fxnhk;#nF%f-*-tF<e9j_ec%vxzp0A5_yV5Da{<UI$3ZT z@+POY!=;t7uj)LYH|OgR0>nyJq}}IiP5|rZd&oa5Y!HKCM^y(22JP3j&yx%l#^^v^ z{))5-uXJT`4RkH1X=B+c3g#i**vI;(v=1(!hH%#lQk}F0E|QpHnPT3T!9XOYiud6# zeB8Q1J}|^qpkaG>?^xG?rX}xcWc1YA_R%iyj<8<;;C1BaR{eOcF_B62y@TPiu%cY0 zE+={xhGt8pQ;iZD+aZrHi=|vra6n%BqgORB7zi`ZCk>8Ty>C>evVJ$$oUW-`csQOu z7;idcxR8AZ#d!hA;9=LL(QsW`5;PYPG%5;E!C<FQMjOzQ07p>Dk4>$=J1c_E0r9Ny z>J=fR^@9E_7!cYKVW<@wV>djreIVh-t{+#o8JLPS=4MCH@>2TruF<kz*fI>xLt?8v zFv4vgI))@L!b0;Qaq_`h9LMV+<VT4E=Qx`N6`KY^-^Mmsq54LDTIE#DI$ibkRey_+ z6idlBH7&n+m^1vGc}Xrs^#|y~ZgBswP*SBG!pP(6y!KWz;4p94VLnOC!H5d0?Kr|~ zK(u4o%D&N5qcxl2Q)ALIpRoqb?`W6j3h@&|H#*x7yrkS07%&||mBNYUfY~ssk%s-W zL(M*$xtOM(%3p~cHYvaG>!6WJZLZb4*&UbOo{NiLjrm%T#^Y3>+j`wvnwn~v$Y$&E z(Qs+Ha*G$I+W*4ICaVc%)aPS7U$wA<4)jSrdL({(lrM$Se!@R^wW(4L%5R+VAL@S) zMUT(xIu_no&b}7s^L}+itv)6!rYIT+bP2EOdtAu9bXEHpVqA#kDE;3$a6^G7=4@!s z*uOL0Z#i^SWTTEv3LfP@ubZp{OIE~vZM>IdrJ$9CG6qPCud0rH?Z5EokHhV3DpaeH zLz{^dPfkjoa-$k}@YyP*ElRqY!;ZU$t>}FhdW@%oR-!!6CB@oI1ID`{)hz8Qj_Xh> z#AEt95mVdAHM|;aG?+Ub{LaE-0VS!1(RD@ptfbfHkz+_*he=bn6q||8TP9r)hwj@) ziWG1W&mqcKXyZT^aUgu9&=qx4cAnFY-4B|#k8Cskyn)7@du}${DKvYiDk=NF;cJ%; zR9v_<9S8J$jhqkzZ?+@jT|bEKa8HHb`Rt!fH&sK-z}jqyAoLMrZV=F2Wx1=@?*N2B zvcT4HJ?#HaSil-@?$Z>`=uO%yEYkyzyf2lbDljrAb>my>P=r*tulF}&0`@%#<ab4i z!}9(0tK^3oG?9)$%c@VgJ?*c)Y`f?EJl`{T{^bY~Xe=UVS$qJ^iq3DdLTF)j_qckP zalP2%N<*ZF?e2kH*09Aq+PS<}%`lw$JbCvMGgv$)N`H>)J*xkeL}IRM5H#BmONLsE zS)I9-W3bhX?9olM9VK16y=}JgIO@@hL4F767<V_Mmz+RXubKdINbfE~z+XW^YYVLz z?}F0r532HYcyp7he$_U5ALDxFP$fd!G8*l7RDOr-*Tm?!#%%W108hu(yy@}G=)+s- zqdk@=4Mm<#^}^EBtd_0|Rdi*_`-+Ri{-ia3rAWnB9rZP!K4+*$eD8fGso$|qFNG{a z^rtF_K-o3lK@NCvzut||pyl*WZ^|#lb3u8?G4cxKg_nQHPJ`Z|oS8f|aVsFHuN~{1 z$RHX8{P?n~jE42@bHy}ZBaMA87Q<a%NjTb3HXwehZh6&jwxRzh+pJHQ4u-2M=CB;G zsGF=>Xrb$=QouZRM<3IEP;vSp=6tV(M){S;Eo_yd<<u_yp&E^10o}d%fz;)R^mA}) zw(w+Ix^%_X18$7>5BIgp2J%yr?m7g@pijbWkKF*CQcc(0S6G_%Fs_HA(IJIbiCez& zkUy@J$?&2D#p)Wa+98skDWQlYJzszH<e=IufyK{585u$3;D=gLyUL0{nd*8+1&t0A z(SW(*vSma2lYiK6D>KpWpH_e`+A2At-2Ctw3gI=2V=X})Uk&bRJO;Oas<$_i9~w%( zldW7laJA@c{|ZBeclSsuNN;h!?~*|8GVtLWj=8+q!#o$3^LXNASXBePy!G#M)Zs94 znb3#PErz4@izY%4<fVSfoA~M3uBV=;7cQ@9{#w%>(}i-Z#jk6PtMPB{pR`>PthZh> z`zeJE53{X3tY}skFpZgUngki=iFCJLDo;V_wv*wAzo)bWF*Z#wT3l)~m)Y*atBQEh znv15vYt1peMbL7|ncD_pBlMmnUJ-Mq#{L_F*n6N7g`q*Wd8D))WUuGyhbBO_2p?Zu z)l_(Y=gN16(){*yvJjzRfWRCYO@(tIz^vDEOuExd<U04@<a?d%yO}sM|Bo#lC!_O{ zDeSt}pA)3Nse;~eF}_6B{5-b~2;2NKo*&Tf2>Qs?#&{DA8eNI;Mei6`%F1(;V9wW+ zCzV|0OkupFY(U;nsW$4w)<pg`AorR!na)_MI3rbZf6?QynQ{-;+8n*C&1>p{Hx`$s zCbisOByXhyPjO`MkHQtLKkb@#WEMu-y%>D{lbttoS-e3f{F=4=>?zO&uO~#=KQKUS z4&GxlcpSL-Pfh&qT{H;STMTo8pdjp*wP|n2$WUc=%o*v1WMk>%a~x0nWS9SJlFUtS zX=+4E=ZUHzsZ!H=(U^50f>iI&r>D0FnEz|!9)YL4M7*cSVi%c%JQ?D-aQ}{)jG%56 z=*0NeiNQImkoo-hTA&g6;q-dgVeTx#o5mtQfZ1Yxe|nD+iNA>hxTG}g+UTI8tbTu0 zdzedMPH?6HmdS^yP`b;}Bu_P3`g66SXI@Si%yIc>75Y#Q2JTp%Bb&1MzH$iM>1Fw+ z*iBvTpX#FXX(vv<lK2w6nzgBLi7RWsIM2_wIZUd16j$=&DZzT~o$tt7<it|%eCd+< z>wwq%KChNygsH_}#C?22vBRx+3i232X;_y*y{|$!^i4ZE7s1+dze<j4{R(w{;Z<I| z0^i6jeSlCy30Wg;AX!S6PQ>a?z$2B9H!J`TN0c`_@XP`q#3M{*b@<t9Rtn?Im-Uxm z1UH)<2cYk8Qd8oSC4z8B#15ghfb_Hd7Fqd33F(2Y@km8nR0HZvT_#UNC1`@1E=4t6 zuXrqU4a^{1id+@%sbOL$raF=@w0TX8hxSrJArs|M45EmM>GjQGI%HO)z;j9c{brX$ z>62kBZZ_<ghoO6Wgr7=$tZ0`s@L(t+MrgqQzG*Nb(iYjn^$!K^8h)K?%Mm_wl7J+M zAtKm(#Ovx<c;jD)&FuqrH8JWc&-l*vQMBr9V5LkcTY&sFKbXqI|Jw2TgH%X_d;3Eq zk$DqO5$S@?QinJo_V|T;biK@ss4eZ-&v3ed9g-cbkmZ&FpbG>fP$-B}l-wJ}GK-|H zYaHa8s%hU~<=01+#_&OmNQ5)<Xr;e(<*wJ8$z{>bN$ufm{1#eGvCqND*AAXD>NDaw z3OaC6H0A1W+Bn4xkU{*$>Q(a8O9i1lA5pG<gq(}&ABhzUz!;81{*4Wn=+g!XDnmte z-N+Ti^yh0xmkE|+A?)UaDKmQSCYiRva$F7c7}UyN<FpxIkL*Re4EzrWNt4ngxp_@~ zI<cSi6A^ckU?1`QBN|-lZ!Hn!@|)X%(dd=j+mFM{922jgNJCz+tM}uC59BfRAJCKt zFc%apKY&}JSO0+xLeDysqc%1Bm^Eem`kBGo0)POe`!awND<74C_nH;C-OewxW4Gm^ zS^NP~+L95Q&}K?8-Oc05E=-6H^n<LpvL%KfRU+i1jZS3{HN3I?KKCB-c75c)n3QL3 z`S`A<&mwz5>^++SbOCNVoB*At5JU5a1M9LB(Ae08;-AH6_XXj<-ro~F&?KdpFw2p8 zX(@a`U^;LT*DzoUM2##MQlWB)f3mbyFavW;MUU9tC44@4!-z9s?Vp=oQ0h`&B(k@1 zAEf1_4GrMFAgkAc?%?uL=#@99L+(F3_Z+@2;k)VFCM8XT@DS*yLU3~cIXp2K3%Uj> z(B>dFigluD=I7QRWl7Cp64vQW+oJWa%}~z!{40Rtuxhx|x`f&v^~`rYlh~w6rM~tk zt-L&`5lI6XZkyEZ^PY@R(*ejTDWlo>pbT;E;CrinegAxy*jGpzW2aGWzjH2{)7@~> zkAKr6@}gbQEFxd2n&3cqF|~p9x)AOWvQTkdFDy?rNFq!=bTM!-?(JhxdZ50pmx9u| ztfx+@IM^VfUMp3JhYww^+@`MbimgGOqi5In-~3X1=q!G@J8%rZ*zfF^)1F*n2J-J8 zDC~>!JT7L8_!7?ITFyep-9;3OJdypU*i@untdmG8^oOYkMq{4MvsGvyu|=^U>&hQH z6_-tN`I$nH$|@MJ73nL;4t9G%O5FYRn!uFacyLau?)AE&MNaHjv{Eh%1AnSsYk#pY zU=&&8k=S4~Fe+^+j3AE1s6$MBk|%HzqiU}yNuS1u<|dlgGxHa2QMPb_3z0W1D);3~ zYyO%f8yq2zm70jlPN|2v5VvV>v~1YO19LHNhh4OF$rn}iQDfp$nlSJt57J2+hc)Oz zOBAous_HRZp={li1A2U<S6i={^(H$wdD*b>3mKzaF&J?N{_ac*rjYW)ylm)Ww?Jo7 zy*kafgKwT^AH+N}#{G&M;@%q~oBGas7b++9t;F&P7dPy(y@ME#-`4zFB<9mWfw+HT z0SZ;npL;O=HfXDp?X_DtTkfbL^@>mjwYTOO${~#ADWB;k4)WjD3t<wL@0h&L5M0C0 z{L}A5r0vNNps^;{$RA--BRasU{peR>uxa%~t`x@pd7)(W6qy>b-M8^%J;m}4+4<!+ zxX96fM%dGskD#>g4aVn5%HC@({nn)iYNgf*4-rzOFN14hC~#-t&CYK4_G%;(-Y!`M z`j;=SwFod(!i13%u_PtuUQ!EizO3L(_^~y9v#244gAsx2%Ga@2wy5%aPj<qu{YM^j zJ?cD>y^jAuW@UB~R4+_)%<VnxZY8Dq9h6dQ>}vktH8Ll0YxBOeiwnPh{G7!EJ!IBD zkEwbM`s;0am1#F;uuJRqfeS)pRaxe5qmi*?0R(*0l=Y8cJY;6rs&;WywQNb2b{Z=e zZ>86=;4mjjGjvBe^w%ADfdoR26KxfE0}ru4U}j9IRwvac88$tKff}w(a&%<O%er<^ zGw~lr3*NsP#KN>Xf!xR8tDxcb7yjtqvdh=Sgy0-nH?o9wQWFoK^|B=Vns=j$oI@fC zy@vB@+Op48SNJ61aNUWh%-|CmQDMuMOXc&~mv09-(*8Ew@o;k5#(Y;oBYB(20MW9T z6v$O&{2XIOBb?#57QK#EC|IK>HgfXf99PscRPMVxsoc)q@2}AE@*^INyk>5yut4Z2 zp0bR0QsX|&``^|xjGiB~O&W6#ms?~S4?k-eUyS^e6bsfL-5Ht>`WGamvHElFGp2=8 z`fF%-nTD85*zC77;}buIN||Q*($AEAfI@sS%(Q|h-{&w0<??M8M(LDf3LwmgpjAg8 z_GSPAQlTLTA|2)JYKm`B(xk{`HeI#Tn|ttCEKmZ7d#U7^Ddzn6K~Z0jk@KSgYe2j3 zq-}S@4bRA?n4!(qXbp#B0j|E~z$ZDqQ8G?U?BXDGlnzwz(aAczyW}4BT{D=87D;Vc zPr)F8WWo;L9%?qC@|(s>-$*I$xfr>xTy5;;tZJd=>5Ti#U1<|!VS)26R0Ij++_<o| zYtoiEWgurDO@N%;b{ygI(j7&al|z&Zm&@6T>r-TnrQn#OUe>t9w_xfVDM|zR`;_d( zOcf?2OJC8@j?@S?aUy?3?P7|Wudz)0C3s(^a9H@{?!xiAlZcHEit*nfzXP+TyQW2- zy8q&tlH7b_$=rGQ?eTyuO1@X|Vr}B#k+ZE7mH_|tmq$f^^JBiVLqD$Z^HZC5=!$qP zmdYr#1X60}KZ;E${i1+482g*r6JNohAtee&Gs2K<FvY?XknnMEgcTe&F8bckDFbyM z4x<`r83U#*pCY}Dum?k^hA`*&!|qo2LRu}!b#m}>)gFY#i5dqEwJsS4nMCRW6A?Z4 zjvKh(ixbrO^b^K`@7?k8boTyB!;D}HS&VxT8p@ixQ2`nvt%ud;^%LuFVlxK3I(V0A z^G!p7UMU04tJl`<A+j>T59KR}g$s!cQGpt829@>Jiz7wM_s25>#|Cht=ZM7n?d9#* zgpV^n@1?qnd9AY)0vn8!m&vf133x*m|9xu?m{X!pYaUGqzGa*o-3R!OTJllV|0m!& z%lRP;@llb~z_9`y!44T&*Y<;vG$7>^XMpMgnK(mjU&?ED^NcBf`dQ|`nD#w03TO>@ zH8yc~D^BZ-g?7vP)C^MBNw?)JVkk|`DPIvRbgoGoH9%3J6oQ9nVO`4Ni&a<<(0Fjz zLbdW*S75Z+=uT)se|_H89e%tW3w>i^!^$-O$7wkC7hpZg>%6bFBw(H};kDboHth0u z=sM~IUxd=};a8;Na-4xec}{)apuWYYt)4$MXKWv|<^cJaNZbc@JHjo)*Bu%X4t=WW zo{A2oqPXyCIaC;m?+pztczKjg6k3ouDc9r=rzvOiJO-pDwFavOkXfeq?glN|_L@M` z$zuY^Cb=0f-P$aU;)t;eolzA@yj-2Ow-D!rzNu4V|M)8t?7w_Sv_jn25{?Wdi#&m? zwCJH>WxP5J_`jSkFO)Y_qy{bSe#n%U!V%q{xaRAV=uZ-A`K%q(PlSGlp{Akm{U?Tn z0@mu7qD@8m=iD1Qwht<s|BO9yyu%_HMoF@F(Qu*hIDCWWY;y{}0+%hiz}nn%poCrz z-z=o{v&Auq%S3hWoc@KITKxU{A!9n*Mj>`<4#mhC#bcpXZHzMusF#SUUk9~p(=91w zEr*O(vD-Z|u5hH^j$XUijsGx7NV1g(OQnwipFP(jv=jxUOQ{7AHvLPJ)~l>LGh$>i zw$vz;Km0Ey)+y%OmQli~Q(QOw4L%zHha+QTBzTbtYMjpy?f$s4AsWjLxZRlg^!V=+ z-W%kf2FYo_F8bLEXQ;-yFX@Mmc@%x;aL?9YUxiKOlpPsQ2Vp^aUkf&aoXMVF1cf&5 z-@PA`p-AkvyR@_bkVQ6(W{6RRF2)V79OT`bD45He+Mqy-WT9{w0;q9@HN>D=csP%n z0Q-vANO2@|<=Dfq_Kq>Y%kwysTv?KpViO}WC7n>Y_2ZDpe~1u^>@#q8a2_-B7IETU zP@^}O6B6i`FJngJ4O{G7SIj6m14B8)6t;-67_R^!5J;C`^$YDbw?BAH??BgYk@Cx4 zoWCz}gKQSm7B4gV?pCh6tJR=#_Q(>1^Xk>`=pz&e7|Vzl=TPb6J2Z;`rsQu;AJ~lw znc+S2W#mqZ=Ml5sJJ=cO1q0>x9?MQ`9+c!Q>|?7uhvuzGb~FL6U--Xgt^l=Se*62u zqz-KlgPOhT$T+-|k)Y3&_s3710hg8a<&s@1^PwHK3`Zd`-5Xp5ZIi`^$BM``|Jwh7 z5PEIP--;M6r97fyskJG9+A8Qchkv`&`p(mDt0i&#GP=b1J2WG|Tl`hZChT*4pOUv5 zFf7UX=d0V$n>;sLg8+}1n$yzvu061s!iOBnWbxC>f7-K)0^hGvw@#P}#?X1PF^@`v z4ODotLK2J=2L20c$xZrj_DPT4v$~}~7b7>|^*=80QJ1upCC~=`BShVZ{V07ryRnCQ zK+R(E;L-24&6YW8#D74>aix-XH&pE|gAbj6BOUlJBYwv6%DzqYflVXpD^?!m#316w z@Q@7lRQaCi8;jU>Yj|kMyX+ePUvsl~-yeeo*(_FTyD|D0ugx5bZN`B9cFev*09-Gz z3bm!{x)X2}gghD^%4pW?dS(3d;!20-Q{;t<wqp-RdG4A&4V+h=0^+%rf9>S8G);;| z2dsMby2!ll<Sa=W$zX*p4E^cS4Fxs`P=_I$R|rB8U8<n}E@$?ZUzs@GyKqgwhZ&g_ z?$kEjwj$U>YF~_%T#MeVnQq8z*lfh;;Rej^q|Lew!=>M+B1rD5QS7E~L^<It%-Hji zsy1%{P9x|cY=n`ez|4Vi+2+wT?*DZArpG&+HB{YuABXBdhP*wl&jU|4?0<hy;Ilia ziH0Bu)f{fv!S4u--Yh;?CxhZWqpCr)%xmR|qW+Tx0ogyel_<OpQ5}tTx(qANiUDxU z9{#g3D;4|1MjXGAG~U3_T4`ji5P%%~MsE`>)a_gF!Iz!vK&aC#p75&pSNx1Mu6?Ke zLrY<aGM4)d(J91N(~J{!XLc3VKLRS|E~f{E`aWE3e%|pV&fm-J;^WQA)fSOT<2jrK z0Yd6dvUh$6jonQB&B4o0-`m!PKb<HZ^J#k$w1GDzd8B%uBKY;1j5JV%&X=y&dndFt z^~r$n0*1mu`l@$jgMBHsZ-Nu;*~o@3+Az}8hyE(CMJ*KEyb!2I?!Nq1-Fr+;^X8CQ zUKuGy4{pPHFLL|np5<kkkhih0L{S(o4ApnWD-)Gr+Cp#lFL6t(|JYAUubCxg_GpDL zo7?y9SPeA0fnAQr+vf~=*!EG%%Io`{<Zebd61fDT%<&EM^|^qYNu%em`~VG_huu5g zya>x)Ck-lnn~ytN+7x%yy)&iMro#vg)Ydq9a$3wfwN73!0`8UquW#`cz5zT~ssjWS z#nAw*T6Z-<Q>;beTDiKLj-h5Y+nWIn|Ih&b@(wy^cqZO|yV=HQLixc<dHY{>O(++% zBD0gIUVf8a*v)^r0KYcy*=&YD<OLaV<!8vE9edKL*DIyDpI{rs`z?K_APuQR)&1^n z&BHYvZ{akZ_fs-ln|^aA%>xe${R(vtdkk@UO)C?U5jm-cgckX_AZA;2w{QFEbhpz1 zxA<KR`{n#A-lPtzl!2=0xvq8jG5Qc=s2|0R(vsm!GuYB|<a`g>ifN9(Svj{}6m29N zl(aq4Vlv%q#VTTkfS9{l(f~vf!b4m*PG2dMiWc&um_PiS$5n4#AOBlqVe=3PqzgoL z1L|0%s+`m1iO*QhV6bUN!{y_-R35bCSD7w#<-!`!Ns^p$9&)gynmKRmNf<q(ZcX?z zH2{RQ0IExU9Zo>O0vJ}+4+H**wCgb|(n<-&bG(^vM$%B(q1E&f)~)6{tG#G;eyuz8 zaEq?}(!r?L=|Vw|WK9r(lLD_ei-1YvYI1iyEm*hOw=2{Mh}cjBs!?mSdt^k#uy=4d zQd7F?ESW<2wHLHz64yr~cnO%s>2CjgS7cHX$$<IqN{a1J+ltKoXYkyWPjf6re#86i ziDL{0)r;;2-(^8j3Nb1O0?JZM8b4b_b=AU{<(PClNjA};aPBVF`t9qYR)#ZCzeW)o z%0RXM(c+~u$7S?I9W_z*WiX=321-~5U`wQ9LDG7rpM*vVW)8@IP5KQM4}pZ%U*Fak zhQJaG<l~2z8pL)M;2$2iry2;11H{2@Kq#WR@vgfU9tabqH0_x*g|_N?;@x%gXqKM= zwY!kFQC%IlY5dZk&+xj`6qM21T&H3lB?5(jc5Gcb5MJn{u0i}hJQnQcrZ~jo8pdgH zRsH5@F=DIk_d|$--=od{Hhl5jT3A7^6JDyX0L9HXH%K9}6wYx<<4D8E(VT-D$E=Og z0JKsU!P|9xr(jQ0l%c>jcHvS0&~-oi!<Z284$enth_zAl`tu*I97Y11p@NQ<D0>Y1 zQZ{3tec>5VDB*u*12pCaGZIb9^=3d2i#5VzQ<E<ShtnFCkRPvniQvZLSV^P;sk>J< zC*1Hl&I<gdLEN-t&fB3{<o!Hv{RCA4II%q`Y)<#(>0aGNJ)x(9D$g`S#eGsa1eki~ z^IMN%XWy5g$-AQ3*hz?Vg!@+m?27D*VM9Fj!3V2gOLod-Gy}C%p|5MC&}5-q(}0=) z;E{DRob!;`<*8X@_OG0=+@_`<q5sZOLh`FiYg92*s4!Z0M~Tu4{VoeIT_9BkGowHT zpViT<3;1u|)r1#=ox=3+ut*AZG|X7aiS!d^c!cS!94N(p1LbV}%uS#_cqGtMuXPR7 z5}QeeY$#G`f&@QN+Af@@14KyxkAuw*dw1-QlU>&W<5M9Uj=g^*ucqPuU5qGx;$)T< zG@&#J8<~CAXT^F}2oT_Y!uM1{ZTmensq1ril#FIE8dc>!o3`OiI(e(9EuVK|(|g`P zVTqgSM=M1EM%2?-76aJj^z;qrAs?X4x(4JNABkXe`Q*A*!BOh6G-YGBv5xkPwJ<?& z80(BJZYImyC^n>@n)aJG5Ncjg9M;sm1cTBHiUO=+2v9UJtSubZwR#H^R!U>*Dj2il z>5@<3d5q!_V0+S0GSl8x(l!0KX+>$OV}%DJqfEoQLT(m2=oz95{I7>yV4UP7`aenu zQe~h7L+U1vFaRP7*3*Yb!M*^hs)UJV=E0VP&>vATv6O=}p+5jcU>d?0Jy+c(r(hz= zf4cj>fW{391<Ct=phT3Z23ks_|H!Y(Mjp!2j4TQknh3fpSE|^$Bd#iEt6+e_pXrp9 z)v^ZFFnO>^%!oXtml}k0jGl+@#k%gM6?Czvp{JIoj=igP#17Y~vY}YtAgZ3gU;&>u zH?zfoe+hwV8KkS2_jXTCu9$pA158^gs2-{Toie2f6C`tuiHiAX^^&cKLYXH<k)|Rs z{=XMvf|SR)#h3-`dT82jjlSb5KJz60u44Itkzjys!?TefYX^S0a#%E5mAH8_|E}I= zu3?CTA+3DmW1)$CG}r)xZ(+Yz30F<m4H{J~Yw1<ZkjBn;dYpedLwi~YZU6u78~y(} cO$$9dcVtt8`^}jD@3b*hB@M+|dGp}^17QH=5dZ)H literal 0 HcmV?d00001 diff --git a/Jellyfin.Server/Migrations/IAsyncMigrationRoutine.cs b/Jellyfin.Server/Migrations/IAsyncMigrationRoutine.cs new file mode 100644 index 00000000..5b6a5fe9 --- /dev/null +++ b/Jellyfin.Server/Migrations/IAsyncMigrationRoutine.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Jellyfin.Server.Migrations; + +/// <summary> +/// Interface that describes a migration routine. +/// </summary> +internal interface IAsyncMigrationRoutine +{ + /// <summary> + /// Execute the migration routine. + /// </summary> + /// <param name="cancellationToken">A cancellation token triggered if the migration should be aborted.</param> + /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> + public Task PerformAsync(CancellationToken cancellationToken); +} + +/// <summary> +/// Interface that describes a migration routine. +/// </summary> +[Obsolete("Use IAsyncMigrationRoutine instead")] +internal interface IMigrationRoutine +{ + /// <summary> + /// Execute the migration routine. + /// </summary> + [Obsolete("Use IAsyncMigrationRoutine.PerformAsync instead")] + public void Perform(); +} diff --git a/Jellyfin.Server/Migrations/IDatabaseMigrationRoutine.cs b/Jellyfin.Server/Migrations/IDatabaseMigrationRoutine.cs new file mode 100644 index 00000000..d2d80a81 --- /dev/null +++ b/Jellyfin.Server/Migrations/IDatabaseMigrationRoutine.cs @@ -0,0 +1,14 @@ +using System; +using Jellyfin.Server.Implementations; +using Microsoft.EntityFrameworkCore; + +namespace Jellyfin.Server.Migrations; + +/// <summary> +/// Defines a migration that operates on the Database. +/// </summary> +#pragma warning disable CS0618 // Type or member is obsolete +internal interface IDatabaseMigrationRoutine : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete +{ +} diff --git a/Jellyfin.Server/Migrations/JellyfinMigrationAttribute.cs b/Jellyfin.Server/Migrations/JellyfinMigrationAttribute.cs new file mode 100644 index 00000000..70e54125 --- /dev/null +++ b/Jellyfin.Server/Migrations/JellyfinMigrationAttribute.cs @@ -0,0 +1,68 @@ +#pragma warning disable CA1019 // Define accessors for attribute arguments + +using System; +using System.Globalization; +using Jellyfin.Server.Migrations.Stages; + +namespace Jellyfin.Server.Migrations; + +/// <summary> +/// Declares an class as an migration with its set metadata. +/// </summary> +[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] +public sealed class JellyfinMigrationAttribute : Attribute +{ + /// <summary> + /// Initializes a new instance of the <see cref="JellyfinMigrationAttribute"/> class. + /// </summary> + /// <param name="order">The ordering this migration should be applied to. Must be a valid DateTime ISO8601 formatted string.</param> + /// <param name="name">The name of this Migration.</param> +#pragma warning disable CS0618 // Type or member is obsolete + public JellyfinMigrationAttribute(string order, string name) : this(order, name, null) +#pragma warning restore CS0618 // Type or member is obsolete + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="JellyfinMigrationAttribute"/> class for legacy migrations. + /// </summary> + /// <param name="order">The ordering this migration should be applied to. Must be a valid DateTime ISO8601 formatted string.</param> + /// <param name="name">The name of this Migration.</param> + /// <param name="key">[ONLY FOR LEGACY MIGRATIONS]The unique key of this migration. Must be a valid Guid formatted string.</param> + [Obsolete("This Constructor should only be used for Legacy migrations. Use the (Order,Name) one for all new ones instead.")] + public JellyfinMigrationAttribute(string order, string name, string? key) + { + Order = DateTime.Parse(order, CultureInfo.InvariantCulture); + Name = name; + Stage = JellyfinMigrationStageTypes.AppInitialisation; + if (key is not null) + { + Key = Guid.Parse(key); + } + } + + /// <summary> + /// Gets or Sets a value indicating whether the annoated migration should be executed on a fresh install. + /// </summary> + public bool RunMigrationOnSetup { get; set; } + + /// <summary> + /// Gets or Sets the stage the annoated migration should be executed at. Defaults to <see cref="JellyfinMigrationStageTypes.CoreInitialisation"/>. + /// </summary> + public JellyfinMigrationStageTypes Stage { get; set; } = JellyfinMigrationStageTypes.CoreInitialisation; + + /// <summary> + /// Gets the ordering of the migration. + /// </summary> + public DateTime Order { get; } + + /// <summary> + /// Gets the name of the migration. + /// </summary> + public string Name { get; } + + /// <summary> + /// Gets the Legacy Key of the migration. Not required for new Migrations. + /// </summary> + public Guid? Key { get; } +} diff --git a/Jellyfin.Server/Migrations/JellyfinMigrationBackupAttribute.cs b/Jellyfin.Server/Migrations/JellyfinMigrationBackupAttribute.cs new file mode 100644 index 00000000..6c8da7e8 --- /dev/null +++ b/Jellyfin.Server/Migrations/JellyfinMigrationBackupAttribute.cs @@ -0,0 +1,35 @@ +using System; + +namespace Jellyfin.Server.Migrations; + +/// <summary> +/// Marks an <see cref="JellyfinMigrationAttribute"/> migration and instructs the <see cref="JellyfinMigrationService"/> to perform a backup. +/// </summary> +[AttributeUsage(System.AttributeTargets.Class, Inherited = true, AllowMultiple = true)] +public sealed class JellyfinMigrationBackupAttribute : System.Attribute +{ + /// <summary> + /// Gets or Sets a value indicating whether a backup of the old library.db should be performed. + /// </summary> + public bool LegacyLibraryDb { get; set; } + + /// <summary> + /// Gets or Sets a value indicating whether a backup of the Database should be performed. + /// </summary> + public bool JellyfinDb { get; set; } + + /// <summary> + /// Gets or Sets a value indicating whether a backup of the metadata folder should be performed. + /// </summary> + public bool Metadata { get; set; } + + /// <summary> + /// Gets or Sets a value indicating whether a backup of the Trickplay folder should be performed. + /// </summary> + public bool Trickplay { get; set; } + + /// <summary> + /// Gets or Sets a value indicating whether a backup of the Subtitles folder should be performed. + /// </summary> + public bool Subtitles { get; set; } +} diff --git a/Jellyfin.Server/Migrations/JellyfinMigrationService.cs b/Jellyfin.Server/Migrations/JellyfinMigrationService.cs new file mode 100644 index 00000000..188d3c4a --- /dev/null +++ b/Jellyfin.Server/Migrations/JellyfinMigrationService.cs @@ -0,0 +1,459 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Implementations.Serialization; +using Jellyfin.Database.Implementations; +using Jellyfin.Server.Implementations.SystemBackupService; +using Jellyfin.Server.Migrations.Stages; +using Jellyfin.Server.ServerSetupApp; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.SystemBackupService; +using MediaBrowser.Model.Configuration; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations; + +/// <summary> +/// Handles Migration of the Jellyfin data structure. +/// </summary> +internal class JellyfinMigrationService +{ + private const string DbFilename = "library.db"; + private readonly IDbContextFactory<JellyfinDbContext> _dbContextFactory; + private readonly ILoggerFactory _loggerFactory; + private readonly IStartupLogger _startupLogger; + private readonly IBackupService? _backupService; + private readonly IJellyfinDatabaseProvider? _jellyfinDatabaseProvider; + private readonly IApplicationPaths _applicationPaths; + private (string? LibraryDb, string? JellyfinDb, BackupManifestDto? FullBackup) _backupKey; + + /// <summary> + /// Initializes a new instance of the <see cref="JellyfinMigrationService"/> class. + /// </summary> + /// <param name="dbContextFactory">Provides access to the jellyfin database.</param> + /// <param name="loggerFactory">The logger factory.</param> + /// <param name="startupLogger">The startup logger for Startup UI intigration.</param> + /// <param name="applicationPaths">Application paths for library.db backup.</param> + /// <param name="backupService">The jellyfin backup service.</param> + /// <param name="jellyfinDatabaseProvider">The jellyfin database provider.</param> + public JellyfinMigrationService( + IDbContextFactory<JellyfinDbContext> dbContextFactory, + ILoggerFactory loggerFactory, + IStartupLogger<JellyfinMigrationService> startupLogger, + IApplicationPaths applicationPaths, + IBackupService? backupService = null, + IJellyfinDatabaseProvider? jellyfinDatabaseProvider = null) + { + _dbContextFactory = dbContextFactory; + _loggerFactory = loggerFactory; + _startupLogger = startupLogger; + _backupService = backupService; + _jellyfinDatabaseProvider = jellyfinDatabaseProvider; + _applicationPaths = applicationPaths; +#pragma warning disable CS0618 // Type or member is obsolete + Migrations = [.. typeof(IMigrationRoutine).Assembly.GetTypes().Where(e => typeof(IMigrationRoutine).IsAssignableFrom(e) || typeof(IAsyncMigrationRoutine).IsAssignableFrom(e)) + .Select(e => (Type: e, Metadata: e.GetCustomAttribute<JellyfinMigrationAttribute>(), Backup: e.GetCustomAttributes<JellyfinMigrationBackupAttribute>())) + .Where(e => e.Metadata is not null) + .GroupBy(e => e.Metadata!.Stage) + .Select(f => + { + var stage = new MigrationStage(f.Key); + foreach (var item in f) + { + JellyfinMigrationBackupAttribute? backupMetadata = null; + if (item.Backup?.Any() == true) + { + backupMetadata = item.Backup.Aggregate(MergeBackupAttributes); + } + + stage.Add(new(item.Type, item.Metadata!, backupMetadata)); + } + + return stage; + })]; +#pragma warning restore CS0618 // Type or member is obsolete + } + + private interface IInternalMigration + { + Task PerformAsync(IStartupLogger logger); + } + + private HashSet<MigrationStage> Migrations { get; set; } + + public async Task CheckFirstTimeRunOrMigration(IApplicationPaths appPaths) + { + var logger = _startupLogger.With(_loggerFactory.CreateLogger<JellyfinMigrationService>()).BeginGroup($"Migration Startup"); + logger.LogInformation("Initialise Migration service."); + var xmlSerializer = new MyXmlSerializer(); + var serverConfig = File.Exists(appPaths.SystemConfigurationFilePath) + ? (ServerConfiguration)xmlSerializer.DeserializeFromFile(typeof(ServerConfiguration), appPaths.SystemConfigurationFilePath)! + : new ServerConfiguration(); + if (!serverConfig.IsStartupWizardCompleted) + { + logger.LogInformation("System initialisation detected. Seed data."); + var flatApplyMigrations = Migrations.SelectMany(e => e.Where(f => !f.Metadata.RunMigrationOnSetup)).ToArray(); + + var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + var databaseCreator = dbContext.Database.GetService<IDatabaseCreator>() as IRelationalDatabaseCreator + ?? throw new InvalidOperationException("Jellyfin does only support relational databases."); + if (!await databaseCreator.ExistsAsync().ConfigureAwait(false)) + { + await databaseCreator.CreateAsync().ConfigureAwait(false); + } + + var historyRepository = dbContext.GetService<IHistoryRepository>(); + + await historyRepository.CreateIfNotExistsAsync().ConfigureAwait(false); + var appliedMigrations = await dbContext.Database.GetAppliedMigrationsAsync().ConfigureAwait(false); + var startupScripts = flatApplyMigrations + .Where(e => !appliedMigrations.Any(f => f != e.BuildCodeMigrationId())) + .Select(e => (Migration: e.Metadata, Script: historyRepository.GetInsertScript(new HistoryRow(e.BuildCodeMigrationId(), GetJellyfinVersion())))) + .ToArray(); + foreach (var item in startupScripts) + { + logger.LogInformation("Seed migration {Key}-{Name}.", item.Migration.Key, item.Migration.Name); + await dbContext.Database.ExecuteSqlRawAsync(item.Script).ConfigureAwait(false); + } + } + + logger.LogInformation("Migration system initialisation completed."); + } + else + { + // migrate any existing migration.xml files + var migrationConfigPath = Path.Join(appPaths.ConfigurationDirectoryPath, "migrations.xml"); + var migrationOptions = File.Exists(migrationConfigPath) + ? (MigrationOptions)xmlSerializer.DeserializeFromFile(typeof(MigrationOptions), migrationConfigPath)! + : null; + if (migrationOptions is not null && migrationOptions.Applied.Count > 0) + { + logger.LogInformation("Old migration style migration.xml detected. Migrate now."); + try + { + var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + var historyRepository = dbContext.GetService<IHistoryRepository>(); + var appliedMigrations = await dbContext.Database.GetAppliedMigrationsAsync().ConfigureAwait(false); + var lastOldAppliedMigration = Migrations + .SelectMany(e => e.Where(e => e.Metadata.Key is not null)) // only consider migrations that have the key set as its the reference marker for legacy migrations. + .Where(e => migrationOptions.Applied.Any(f => f.Id.Equals(e.Metadata.Key!.Value))) + .Where(e => !appliedMigrations.Contains(e.BuildCodeMigrationId())) + .OrderBy(e => e.BuildCodeMigrationId()) + .Last(); // this is the latest migration applied in the old migration.xml + + IReadOnlyList<CodeMigration> oldMigrations = [ + .. Migrations + .SelectMany(e => e) + .OrderBy(e => e.BuildCodeMigrationId()) + .TakeWhile(e => e.BuildCodeMigrationId() != lastOldAppliedMigration.BuildCodeMigrationId()), + lastOldAppliedMigration + ]; + // those are all migrations that had to run in the old migration system, even if not noted in the migration.xml file. + + var startupScripts = oldMigrations.Select(e => (Migration: e.Metadata, Script: historyRepository.GetInsertScript(new HistoryRow(e.BuildCodeMigrationId(), GetJellyfinVersion())))); + foreach (var item in startupScripts) + { + logger.LogInformation("Migrate migration {Key}-{Name}.", item.Migration.Key, item.Migration.Name); + await dbContext.Database.ExecuteSqlRawAsync(item.Script).ConfigureAwait(false); + } + + logger.LogInformation("Rename old migration.xml to migration.xml.backup"); + File.Move(migrationConfigPath, Path.ChangeExtension(migrationConfigPath, ".xml.backup"), true); + } + } + catch (Exception ex) + { + logger.LogCritical(ex, "Failed to apply migrations"); + throw; + } + } + } + } + + public async Task MigrateStepAsync(JellyfinMigrationStageTypes stage, IServiceProvider? serviceProvider) + { + var logger = _startupLogger.With(_loggerFactory.CreateLogger<JellyfinMigrationService>()).BeginGroup($"Migrate stage {stage}."); + ICollection<CodeMigration> migrationStage = (Migrations.FirstOrDefault(e => e.Stage == stage) as ICollection<CodeMigration>) ?? []; + + var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + var historyRepository = dbContext.GetService<IHistoryRepository>(); + var migrationsAssembly = dbContext.GetService<IMigrationsAssembly>(); + var appliedMigrations = await historyRepository.GetAppliedMigrationsAsync().ConfigureAwait(false); + var pendingCodeMigrations = migrationStage + .Where(e => appliedMigrations.All(f => f.MigrationId != e.BuildCodeMigrationId())) + .Select(e => (Key: e.BuildCodeMigrationId(), Migration: new InternalCodeMigration(e, serviceProvider, dbContext))) + .ToArray(); + + (string Key, InternalDatabaseMigration Migration)[] pendingDatabaseMigrations = []; + if (stage is JellyfinMigrationStageTypes.CoreInitialisation) + { + pendingDatabaseMigrations = migrationsAssembly.Migrations.Where(f => appliedMigrations.All(e => e.MigrationId != f.Key)) + .Select(e => (Key: e.Key, Migration: new InternalDatabaseMigration(e, dbContext))) + .ToArray(); + } + + (string Key, IInternalMigration Migration)[] pendingMigrations = [.. pendingCodeMigrations, .. pendingDatabaseMigrations]; + logger.LogInformation("There are {Pending} migrations for stage {Stage}.", pendingCodeMigrations.Length, stage); + var migrations = pendingMigrations.OrderBy(e => e.Key).ToArray(); + + foreach (var item in migrations) + { + var migrationLogger = logger.With(_loggerFactory.CreateLogger(item.Migration.GetType().Name)).BeginGroup($"{item.Key}"); + try + { + migrationLogger.LogInformation("Perform migration {Name}", item.Key); + await item.Migration.PerformAsync(migrationLogger).ConfigureAwait(false); + migrationLogger.LogInformation("Migration {Name} was successfully applied", item.Key); + } + catch (Exception ex) + { + migrationLogger.LogCritical("Error: {Error}", ex.Message); + migrationLogger.LogError(ex, "Migration {Name} failed", item.Key); + + if (_backupKey != default && _backupService is not null && _jellyfinDatabaseProvider is not null) + { + if (_backupKey.LibraryDb is not null) + { + migrationLogger.LogInformation("Attempt to rollback librarydb."); + try + { + var libraryDbPath = Path.Combine(_applicationPaths.DataPath, DbFilename); + File.Move(_backupKey.LibraryDb, libraryDbPath, true); + } + catch (Exception inner) + { + migrationLogger.LogCritical(inner, "Could not rollback {LibraryPath}. Manual intervention might be required to restore a operational state.", _backupKey.LibraryDb); + } + } + + if (_backupKey.JellyfinDb is not null) + { + migrationLogger.LogInformation("Attempt to rollback JellyfinDb."); + try + { + await _jellyfinDatabaseProvider.RestoreBackupFast(_backupKey.JellyfinDb, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception inner) + { + migrationLogger.LogCritical(inner, "Could not rollback {LibraryPath}. Manual intervention might be required to restore a operational state.", _backupKey.JellyfinDb); + } + } + + if (_backupKey.FullBackup is not null) + { + migrationLogger.LogInformation("Attempt to rollback from backup."); + try + { + await _backupService.RestoreBackupAsync(_backupKey.FullBackup.Path).ConfigureAwait(false); + } + catch (Exception inner) + { + migrationLogger.LogCritical(inner, "Could not rollback from backup {Backup}. Manual intervention might be required to restore a operational state.", _backupKey.FullBackup.Path); + } + } + } + + throw; + } + } + } + } + + private static string GetJellyfinVersion() + { + return Assembly.GetEntryAssembly()!.GetName().Version!.ToString(); + } + + public async Task CleanupSystemAfterMigration(ILogger logger) + { + if (_backupKey != default) + { + if (_backupKey.LibraryDb is not null) + { + logger.LogInformation("Attempt to cleanup librarydb backup."); + try + { + File.Delete(_backupKey.LibraryDb); + } + catch (Exception inner) + { + logger.LogCritical(inner, "Could not cleanup {LibraryPath}.", _backupKey.LibraryDb); + } + } + + if (_backupKey.JellyfinDb is not null && _jellyfinDatabaseProvider is not null) + { + logger.LogInformation("Attempt to cleanup JellyfinDb backup."); + try + { + await _jellyfinDatabaseProvider.DeleteBackup(_backupKey.JellyfinDb).ConfigureAwait(false); + } + catch (Exception inner) + { + logger.LogCritical(inner, "Could not cleanup {LibraryPath}.", _backupKey.JellyfinDb); + } + } + + if (_backupKey.FullBackup is not null) + { + logger.LogInformation("Attempt to cleanup from migration backup."); + try + { + File.Delete(_backupKey.FullBackup.Path); + } + catch (Exception inner) + { + logger.LogCritical(inner, "Could not cleanup backup {Backup}.", _backupKey.FullBackup.Path); + } + } + } + } + + public async Task PrepareSystemForMigration(ILogger logger) + { + logger.LogInformation("Prepare system for possible migrations"); + JellyfinMigrationBackupAttribute backupInstruction; + IReadOnlyList<HistoryRow> appliedMigrations; + var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + var historyRepository = dbContext.GetService<IHistoryRepository>(); + var migrationsAssembly = dbContext.GetService<IMigrationsAssembly>(); + appliedMigrations = await historyRepository.GetAppliedMigrationsAsync().ConfigureAwait(false); + backupInstruction = new JellyfinMigrationBackupAttribute() + { + JellyfinDb = migrationsAssembly.Migrations.Any(f => appliedMigrations.All(e => e.MigrationId != f.Key)) + }; + } + + backupInstruction = Migrations.SelectMany(e => e) + .Where(e => appliedMigrations.All(f => f.MigrationId != e.BuildCodeMigrationId())) + .Select(e => e.BackupRequirements) + .Where(e => e is not null) + .Aggregate(backupInstruction, MergeBackupAttributes!); + + if (backupInstruction.LegacyLibraryDb) + { + logger.LogInformation("A migration will attempt to modify the library.db, will attempt to backup the file now."); + // for legacy migrations that still operates on the library.db + var libraryDbPath = Path.Combine(_applicationPaths.DataPath, DbFilename); + if (File.Exists(libraryDbPath)) + { + for (int i = 1; ; i++) + { + var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", libraryDbPath, i); + if (!File.Exists(bakPath)) + { + try + { + logger.LogInformation("Backing up {Library} to {BackupPath}", DbFilename, bakPath); + File.Copy(libraryDbPath, bakPath); + _backupKey = (bakPath, _backupKey.JellyfinDb, _backupKey.FullBackup); + logger.LogInformation("{Library} backed up to {BackupPath}", DbFilename, bakPath); + break; + } + catch (Exception ex) + { + logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath); + throw; + } + } + } + + logger.LogInformation("{Library} has been backed up as {BackupPath}", DbFilename, _backupKey.LibraryDb); + } + else + { + logger.LogError("Cannot make a backup of {Library} at path {BackupPath} because file could not be found at {LibraryPath}", DbFilename, libraryDbPath, _applicationPaths.DataPath); + } + } + + if (backupInstruction.JellyfinDb && _jellyfinDatabaseProvider is not null) + { + logger.LogInformation("A migration will attempt to modify the jellyfin.db, will attempt to backup the file now."); + _backupKey = (_backupKey.LibraryDb, await _jellyfinDatabaseProvider.MigrationBackupFast(CancellationToken.None).ConfigureAwait(false), _backupKey.FullBackup); + logger.LogInformation("Jellyfin database has been backed up as {BackupPath}", _backupKey.JellyfinDb); + } + + if (_backupService is not null && (backupInstruction.Metadata || backupInstruction.Subtitles || backupInstruction.Trickplay)) + { + logger.LogInformation("A migration will attempt to modify system resources. Will attempt to create backup now."); + _backupKey = (_backupKey.LibraryDb, _backupKey.JellyfinDb, await _backupService.CreateBackupAsync(new BackupOptionsDto() + { + Metadata = backupInstruction.Metadata, + Subtitles = backupInstruction.Subtitles, + Trickplay = backupInstruction.Trickplay, + Database = false // database backups are explicitly handled by the provider itself as the backup service requires parity with the current model + }).ConfigureAwait(false)); + logger.LogInformation("Pre-Migration backup successfully created as {BackupKey}", _backupKey.FullBackup.Path); + } + } + + private static JellyfinMigrationBackupAttribute MergeBackupAttributes(JellyfinMigrationBackupAttribute left, JellyfinMigrationBackupAttribute right) + { + return new JellyfinMigrationBackupAttribute() + { + JellyfinDb = left!.JellyfinDb || right!.JellyfinDb, + LegacyLibraryDb = left.LegacyLibraryDb || right!.LegacyLibraryDb, + Metadata = left.Metadata || right!.Metadata, + Subtitles = left.Subtitles || right!.Subtitles, + Trickplay = left.Trickplay || right!.Trickplay + }; + } + + private class InternalCodeMigration : IInternalMigration + { + private readonly CodeMigration _codeMigration; + private readonly IServiceProvider? _serviceProvider; + private JellyfinDbContext _dbContext; + + public InternalCodeMigration(CodeMigration codeMigration, IServiceProvider? serviceProvider, JellyfinDbContext dbContext) + { + _codeMigration = codeMigration; + _serviceProvider = serviceProvider; + _dbContext = dbContext; + } + + public async Task PerformAsync(IStartupLogger logger) + { + await _codeMigration.Perform(_serviceProvider, logger, CancellationToken.None).ConfigureAwait(false); + + var historyRepository = _dbContext.GetService<IHistoryRepository>(); + var createScript = historyRepository.GetInsertScript(new HistoryRow(_codeMigration.BuildCodeMigrationId(), GetJellyfinVersion())); + await _dbContext.Database.ExecuteSqlRawAsync(createScript).ConfigureAwait(false); + } + } + + private class InternalDatabaseMigration : IInternalMigration + { + private readonly JellyfinDbContext _jellyfinDbContext; + private KeyValuePair<string, TypeInfo> _databaseMigrationInfo; + + public InternalDatabaseMigration(KeyValuePair<string, TypeInfo> databaseMigrationInfo, JellyfinDbContext jellyfinDbContext) + { + _databaseMigrationInfo = databaseMigrationInfo; + _jellyfinDbContext = jellyfinDbContext; + } + + public async Task PerformAsync(IStartupLogger logger) + { + var migrator = _jellyfinDbContext.GetService<IMigrator>(); + await migrator.MigrateAsync(_databaseMigrationInfo.Key).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server/Migrations/MigrationOptions.cs b/Jellyfin.Server/Migrations/MigrationOptions.cs new file mode 100644 index 00000000..c9710f1f --- /dev/null +++ b/Jellyfin.Server/Migrations/MigrationOptions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; + +namespace Jellyfin.Server.Migrations +{ + /// <summary> + /// Configuration part that holds all migrations that were applied. + /// </summary> + public class MigrationOptions + { + /// <summary> + /// Initializes a new instance of the <see cref="MigrationOptions"/> class. + /// </summary> + public MigrationOptions() + { + Applied = new List<(Guid Id, string Name)>(); + } + +// .Net xml serializer can't handle interfaces +#pragma warning disable CA1002 // Do not expose generic lists + /// <summary> + /// Gets the list of applied migration routine names. + /// </summary> + public List<(Guid Id, string Name)> Applied { get; } +#pragma warning restore CA1002 + } +} diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs new file mode 100644 index 00000000..fd472cff --- /dev/null +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs @@ -0,0 +1,129 @@ +using System; +using System.IO; +using System.Xml; +using System.Xml.Serialization; +using Emby.Server.Implementations; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.PreStartupRoutines; + +/// <inheritdoc /> +#pragma warning disable CS0618 // Type or member is obsolete +[JellyfinMigration("2025-04-20T00:00:00", nameof(CreateNetworkConfiguration), "9B354818-94D5-4B68-AC49-E35CB85F9D84", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)] +public class CreateNetworkConfiguration : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete +{ + private readonly ServerApplicationPaths _applicationPaths; + private readonly ILogger<CreateNetworkConfiguration> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="CreateNetworkConfiguration"/> class. + /// </summary> + /// <param name="applicationPaths">An instance of <see cref="ServerApplicationPaths"/>.</param> + /// <param name="loggerFactory">An instance of the <see cref="ILoggerFactory"/> interface.</param> + public CreateNetworkConfiguration(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory) + { + _applicationPaths = applicationPaths; + _logger = loggerFactory.CreateLogger<CreateNetworkConfiguration>(); + } + + /// <inheritdoc /> + public void Perform() + { + string path = Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "network.xml"); + if (File.Exists(path)) + { + _logger.LogDebug("Network configuration file already exists, skipping"); + return; + } + + var serverConfigSerializer = new XmlSerializer(typeof(OldNetworkConfiguration), new XmlRootAttribute("ServerConfiguration")); + using var xmlReader = XmlReader.Create(_applicationPaths.SystemConfigurationFilePath); + var networkSettings = serverConfigSerializer.Deserialize(xmlReader); + + var networkConfigSerializer = new XmlSerializer(typeof(OldNetworkConfiguration), new XmlRootAttribute("NetworkConfiguration")); + var xmlWriterSettings = new XmlWriterSettings { Indent = true }; + using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings); + networkConfigSerializer.Serialize(xmlWriter, networkSettings); + } + +#pragma warning disable + public sealed class OldNetworkConfiguration + { + public const int DefaultHttpPort = 8096; + + public const int DefaultHttpsPort = 8920; + + private string _baseUrl = string.Empty; + + public bool RequireHttps { get; set; } + + public string CertificatePath { get; set; } = string.Empty; + + public string CertificatePassword { get; set; } = string.Empty; + + public string BaseUrl + { + get => _baseUrl; + set + { + // Normalize the start of the string + if (string.IsNullOrWhiteSpace(value)) + { + // If baseUrl is empty, set an empty prefix string + _baseUrl = string.Empty; + return; + } + + if (value[0] != '/') + { + // If baseUrl was not configured with a leading slash, append one for consistency + value = "/" + value; + } + + // Normalize the end of the string + if (value[^1] == '/') + { + // If baseUrl was configured with a trailing slash, remove it for consistency + value = value.Remove(value.Length - 1); + } + + _baseUrl = value; + } + } + + public int PublicHttpsPort { get; set; } = DefaultHttpsPort; + + public int HttpServerPortNumber { get; set; } = DefaultHttpPort; + + public int HttpsPortNumber { get; set; } = DefaultHttpsPort; + + public bool EnableHttps { get; set; } + + public int PublicPort { get; set; } = DefaultHttpPort; + + public bool EnableIPV6 { get; set; } + + public bool EnableIPV4 { get; set; } = true; + + public bool IgnoreVirtualInterfaces { get; set; } = true; + + public string[] VirtualInterfaceNames { get; set; } = new string[] { "veth" }; + + public string[] PublishedServerUriBySubnet { get; set; } = Array.Empty<string>(); + + public string[] RemoteIPFilter { get; set; } = Array.Empty<string>(); + + public bool IsRemoteIPFilterBlacklist { get; set; } + + public bool EnableUPnP { get; set; } + + public bool EnableRemoteAccess { get; set; } = true; + + public string[] LocalNetworkSubnets { get; set; } = Array.Empty<string>(); + + public string[] LocalNetworkAddresses { get; set; } = Array.Empty<string>(); + + public string[] KnownProxies { get; set; } = Array.Empty<string>(); + } +} diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs new file mode 100644 index 00000000..0141b43c --- /dev/null +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs @@ -0,0 +1,239 @@ +using System; +using System.IO; +using System.Xml; +using System.Xml.Serialization; +using Emby.Server.Implementations; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Entities; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.PreStartupRoutines; + +/// <inheritdoc /> +#pragma warning disable CS0618 // Type or member is obsolete +[JellyfinMigration("2025-04-20T03:00:00", nameof(MigrateEncodingOptions), "A8E61960-7726-4450-8F3D-82C12DAABBCB", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)] +public class MigrateEncodingOptions : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete +{ + private readonly ServerApplicationPaths _applicationPaths; + private readonly ILogger<MigrateEncodingOptions> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="MigrateEncodingOptions"/> class. + /// </summary> + /// <param name="applicationPaths">An instance of <see cref="ServerApplicationPaths"/>.</param> + /// <param name="loggerFactory">An instance of the <see cref="ILoggerFactory"/> interface.</param> + public MigrateEncodingOptions(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory) + { + _applicationPaths = applicationPaths; + _logger = loggerFactory.CreateLogger<MigrateEncodingOptions>(); + } + + /// <inheritdoc /> + public void Perform() + { + string path = Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "encoding.xml"); + var oldSerializer = new XmlSerializer(typeof(OldEncodingOptions), new XmlRootAttribute("EncodingOptions")); + OldEncodingOptions? oldConfig = null; + + try + { + using var xmlReader = XmlReader.Create(path); + oldConfig = (OldEncodingOptions?)oldSerializer.Deserialize(xmlReader); + } + catch (InvalidOperationException ex) + { + _logger.LogError(ex, "Migrate EncodingOptions deserialize Invalid Operation error"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Migrate EncodingOptions deserialize error"); + } + + if (oldConfig is null) + { + return; + } + + var hardwareAccelerationType = HardwareAccelerationType.none; + if (Enum.TryParse<HardwareAccelerationType>(oldConfig.HardwareAccelerationType, true, out var parsedHardwareAccelerationType)) + { + hardwareAccelerationType = parsedHardwareAccelerationType; + } + + var tonemappingAlgorithm = TonemappingAlgorithm.none; + if (Enum.TryParse<TonemappingAlgorithm>(oldConfig.TonemappingAlgorithm, true, out var parsedTonemappingAlgorithm)) + { + tonemappingAlgorithm = parsedTonemappingAlgorithm; + } + + var tonemappingMode = TonemappingMode.auto; + if (Enum.TryParse<TonemappingMode>(oldConfig.TonemappingMode, true, out var parsedTonemappingMode)) + { + tonemappingMode = parsedTonemappingMode; + } + + var tonemappingRange = TonemappingRange.auto; + if (Enum.TryParse<TonemappingRange>(oldConfig.TonemappingRange, true, out var parsedTonemappingRange)) + { + tonemappingRange = parsedTonemappingRange; + } + + var encoderPreset = EncoderPreset.superfast; + if (Enum.TryParse<EncoderPreset>(oldConfig.TonemappingRange, true, out var parsedEncoderPreset)) + { + encoderPreset = parsedEncoderPreset; + } + + var deinterlaceMethod = DeinterlaceMethod.yadif; + if (Enum.TryParse<DeinterlaceMethod>(oldConfig.TonemappingRange, true, out var parsedDeinterlaceMethod)) + { + deinterlaceMethod = parsedDeinterlaceMethod; + } + + var encodingOptions = new EncodingOptions() + { + EncodingThreadCount = oldConfig.EncodingThreadCount, + TranscodingTempPath = oldConfig.TranscodingTempPath, + FallbackFontPath = oldConfig.FallbackFontPath, + EnableFallbackFont = oldConfig.EnableFallbackFont, + EnableAudioVbr = oldConfig.EnableAudioVbr, + DownMixAudioBoost = oldConfig.DownMixAudioBoost, + DownMixStereoAlgorithm = oldConfig.DownMixStereoAlgorithm, + MaxMuxingQueueSize = oldConfig.MaxMuxingQueueSize, + EnableThrottling = oldConfig.EnableThrottling, + ThrottleDelaySeconds = oldConfig.ThrottleDelaySeconds, + EnableSegmentDeletion = oldConfig.EnableSegmentDeletion, + SegmentKeepSeconds = oldConfig.SegmentKeepSeconds, + HardwareAccelerationType = hardwareAccelerationType, + EncoderAppPath = oldConfig.EncoderAppPath, + EncoderAppPathDisplay = oldConfig.EncoderAppPathDisplay, + VaapiDevice = oldConfig.VaapiDevice, + EnableTonemapping = oldConfig.EnableTonemapping, + EnableVppTonemapping = oldConfig.EnableVppTonemapping, + EnableVideoToolboxTonemapping = oldConfig.EnableVideoToolboxTonemapping, + TonemappingAlgorithm = tonemappingAlgorithm, + TonemappingMode = tonemappingMode, + TonemappingRange = tonemappingRange, + TonemappingDesat = oldConfig.TonemappingDesat, + TonemappingPeak = oldConfig.TonemappingPeak, + TonemappingParam = oldConfig.TonemappingParam, + VppTonemappingBrightness = oldConfig.VppTonemappingBrightness, + VppTonemappingContrast = oldConfig.VppTonemappingContrast, + H264Crf = oldConfig.H264Crf, + H265Crf = oldConfig.H265Crf, + EncoderPreset = encoderPreset, + DeinterlaceDoubleRate = oldConfig.DeinterlaceDoubleRate, + DeinterlaceMethod = deinterlaceMethod, + EnableDecodingColorDepth10Hevc = oldConfig.EnableDecodingColorDepth10Hevc, + EnableDecodingColorDepth10Vp9 = oldConfig.EnableDecodingColorDepth10Vp9, + EnableEnhancedNvdecDecoder = oldConfig.EnableEnhancedNvdecDecoder, + PreferSystemNativeHwDecoder = oldConfig.PreferSystemNativeHwDecoder, + EnableIntelLowPowerH264HwEncoder = oldConfig.EnableIntelLowPowerH264HwEncoder, + EnableIntelLowPowerHevcHwEncoder = oldConfig.EnableIntelLowPowerHevcHwEncoder, + EnableHardwareEncoding = oldConfig.EnableHardwareEncoding, + AllowHevcEncoding = oldConfig.AllowHevcEncoding, + AllowAv1Encoding = oldConfig.AllowAv1Encoding, + EnableSubtitleExtraction = oldConfig.EnableSubtitleExtraction, + HardwareDecodingCodecs = oldConfig.HardwareDecodingCodecs, + AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = oldConfig.AllowOnDemandMetadataBasedKeyframeExtractionForExtensions + }; + + var newSerializer = new XmlSerializer(typeof(EncodingOptions)); + var xmlWriterSettings = new XmlWriterSettings { Indent = true }; + using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings); + newSerializer.Serialize(xmlWriter, encodingOptions); + } + +#pragma warning disable + public sealed class OldEncodingOptions + { + public int EncodingThreadCount { get; set; } + + public string TranscodingTempPath { get; set; } + + public string FallbackFontPath { get; set; } + + public bool EnableFallbackFont { get; set; } + + public bool EnableAudioVbr { get; set; } + + public double DownMixAudioBoost { get; set; } + + public DownMixStereoAlgorithms DownMixStereoAlgorithm { get; set; } + + public int MaxMuxingQueueSize { get; set; } + + public bool EnableThrottling { get; set; } + + public int ThrottleDelaySeconds { get; set; } + + public bool EnableSegmentDeletion { get; set; } + + public int SegmentKeepSeconds { get; set; } + + public string HardwareAccelerationType { get; set; } + + public string EncoderAppPath { get; set; } + + public string EncoderAppPathDisplay { get; set; } + + public string VaapiDevice { get; set; } + + public bool EnableTonemapping { get; set; } + + public bool EnableVppTonemapping { get; set; } + + public bool EnableVideoToolboxTonemapping { get; set; } + + public string TonemappingAlgorithm { get; set; } + + public string TonemappingMode { get; set; } + + public string TonemappingRange { get; set; } + + public double TonemappingDesat { get; set; } + + public double TonemappingPeak { get; set; } + + public double TonemappingParam { get; set; } + + public double VppTonemappingBrightness { get; set; } + + public double VppTonemappingContrast { get; set; } + + public int H264Crf { get; set; } + + public int H265Crf { get; set; } + + public string EncoderPreset { get; set; } + + public bool DeinterlaceDoubleRate { get; set; } + + public string DeinterlaceMethod { get; set; } + + public bool EnableDecodingColorDepth10Hevc { get; set; } + + public bool EnableDecodingColorDepth10Vp9 { get; set; } + + public bool EnableEnhancedNvdecDecoder { get; set; } + + public bool PreferSystemNativeHwDecoder { get; set; } + + public bool EnableIntelLowPowerH264HwEncoder { get; set; } + + public bool EnableIntelLowPowerHevcHwEncoder { get; set; } + + public bool EnableHardwareEncoding { get; set; } + + public bool AllowHevcEncoding { get; set; } + + public bool AllowAv1Encoding { get; set; } + + public bool EnableSubtitleExtraction { get; set; } + + public string[] HardwareDecodingCodecs { get; set; } + + public string[] AllowOnDemandMetadataBasedKeyframeExtractionForExtensions { get; set; } + } +} diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs new file mode 100644 index 00000000..e8da9f51 --- /dev/null +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs @@ -0,0 +1,92 @@ +using System; +using System.IO; +using System.Xml; +using System.Xml.Serialization; +using Emby.Server.Implementations; +using MediaBrowser.Providers.Plugins.MusicBrainz.Configuration; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.PreStartupRoutines; + +/// <inheritdoc /> +#pragma warning disable CS0618 // Type or member is obsolete +[JellyfinMigration("2025-04-20T02:00:00", nameof(MigrateMusicBrainzTimeout), "A6DCACF4-C057-4Ef9-80D3-61CEF9DDB4F0", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)] +public class MigrateMusicBrainzTimeout : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete +{ + private readonly ServerApplicationPaths _applicationPaths; + private readonly ILogger<MigrateMusicBrainzTimeout> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="MigrateMusicBrainzTimeout"/> class. + /// </summary> + /// <param name="applicationPaths">An instance of <see cref="ServerApplicationPaths"/>.</param> + /// <param name="loggerFactory">An instance of the <see cref="ILoggerFactory"/> interface.</param> + public MigrateMusicBrainzTimeout(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory) + { + _applicationPaths = applicationPaths; + _logger = loggerFactory.CreateLogger<MigrateMusicBrainzTimeout>(); + } + + /// <inheritdoc /> + public void Perform() + { + string path = Path.Combine(_applicationPaths.PluginConfigurationsPath, "Jellyfin.Plugin.MusicBrainz.xml"); + if (!File.Exists(path)) + { + _logger.LogDebug("No MusicBrainz plugin configuration file found, skipping"); + return; + } + + var oldPluginConfiguration = ReadOld(path); + + if (oldPluginConfiguration is not null) + { + var newPluginConfiguration = new PluginConfiguration + { + Server = oldPluginConfiguration.Server, + ReplaceArtistName = oldPluginConfiguration.ReplaceArtistName + }; + var newRateLimit = oldPluginConfiguration.RateLimit / 1000.0; + newPluginConfiguration.RateLimit = newRateLimit < 1.0 ? 1.0 : newRateLimit; + WriteNew(path, newPluginConfiguration); + } + } + + private OldMusicBrainzConfiguration? ReadOld(string path) + { + using var xmlReader = XmlReader.Create(path); + var serverConfigSerializer = new XmlSerializer(typeof(OldMusicBrainzConfiguration), new XmlRootAttribute("PluginConfiguration")); + return serverConfigSerializer.Deserialize(xmlReader) as OldMusicBrainzConfiguration; + } + + private void WriteNew(string path, PluginConfiguration newPluginConfiguration) + { + var pluginConfigurationSerializer = new XmlSerializer(typeof(PluginConfiguration), new XmlRootAttribute("PluginConfiguration")); + var xmlWriterSettings = new XmlWriterSettings { Indent = true }; + using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings); + pluginConfigurationSerializer.Serialize(xmlWriter, newPluginConfiguration); + } + +#pragma warning disable + public sealed class OldMusicBrainzConfiguration + { + private string _server = string.Empty; + + private long _rateLimit = 0L; + + public string Server + { + get => _server; + set => _server = value.TrimEnd('/'); + } + + public long RateLimit + { + get => _rateLimit; + set => _rateLimit = value; + } + + public bool ReplaceArtistName { get; set; } + } +} diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs new file mode 100644 index 00000000..f2790c1a --- /dev/null +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs @@ -0,0 +1,205 @@ +#pragma warning disable CS0618 // obsolete + +using System; +using System.IO; +using System.Xml; +using System.Xml.Serialization; +using Emby.Server.Implementations; +using MediaBrowser.Common.Net; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.PreStartupRoutines; + +/// <inheritdoc /> +[JellyfinMigration("2025-04-20T01:00:00", nameof(MigrateNetworkConfiguration), "4FB5C950-1991-11EE-9B4B-0800200C9A66", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)] +public class MigrateNetworkConfiguration : IMigrationRoutine +{ + private readonly ServerApplicationPaths _applicationPaths; + private readonly ILogger<MigrateNetworkConfiguration> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="MigrateNetworkConfiguration"/> class. + /// </summary> + /// <param name="applicationPaths">An instance of <see cref="ServerApplicationPaths"/>.</param> + /// <param name="loggerFactory">An instance of the <see cref="ILoggerFactory"/> interface.</param> + public MigrateNetworkConfiguration(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory) + { + _applicationPaths = applicationPaths; + _logger = loggerFactory.CreateLogger<MigrateNetworkConfiguration>(); + } + + /// <inheritdoc /> + public void Perform() + { + string path = Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "network.xml"); + var oldNetworkConfigSerializer = new XmlSerializer(typeof(OldNetworkConfiguration), new XmlRootAttribute("NetworkConfiguration")); + OldNetworkConfiguration? oldNetworkConfiguration = null; + + try + { + using var xmlReader = XmlReader.Create(path); + oldNetworkConfiguration = (OldNetworkConfiguration?)oldNetworkConfigSerializer.Deserialize(xmlReader); + } + catch (InvalidOperationException ex) + { + _logger.LogError(ex, "Migrate NetworkConfiguration deserialize Invalid Operation error"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Migrate NetworkConfiguration deserialize error"); + } + + if (oldNetworkConfiguration is null) + { + return; + } + + // Migrate network config values to new config schema + var networkConfiguration = new NetworkConfiguration + { + AutoDiscovery = oldNetworkConfiguration.AutoDiscovery, + BaseUrl = oldNetworkConfiguration.BaseUrl, + CertificatePassword = oldNetworkConfiguration.CertificatePassword, + CertificatePath = oldNetworkConfiguration.CertificatePath, + EnableHttps = oldNetworkConfiguration.EnableHttps, + EnableIPv4 = oldNetworkConfiguration.EnableIPV4, + EnableIPv6 = oldNetworkConfiguration.EnableIPV6, + EnablePublishedServerUriByRequest = oldNetworkConfiguration.EnablePublishedServerUriByRequest, + EnableRemoteAccess = oldNetworkConfiguration.EnableRemoteAccess, + EnableUPnP = oldNetworkConfiguration.EnableUPnP, + IgnoreVirtualInterfaces = oldNetworkConfiguration.IgnoreVirtualInterfaces, + InternalHttpPort = oldNetworkConfiguration.HttpServerPortNumber, + InternalHttpsPort = oldNetworkConfiguration.HttpsPortNumber, + IsRemoteIPFilterBlacklist = oldNetworkConfiguration.IsRemoteIPFilterBlacklist, + KnownProxies = oldNetworkConfiguration.KnownProxies, + LocalNetworkAddresses = oldNetworkConfiguration.LocalNetworkAddresses, + LocalNetworkSubnets = oldNetworkConfiguration.LocalNetworkSubnets, + PublicHttpPort = oldNetworkConfiguration.PublicPort, + PublicHttpsPort = oldNetworkConfiguration.PublicHttpsPort, + PublishedServerUriBySubnet = oldNetworkConfiguration.PublishedServerUriBySubnet, + RemoteIPFilter = oldNetworkConfiguration.RemoteIPFilter, + RequireHttps = oldNetworkConfiguration.RequireHttps + }; + + // Migrate old virtual interface name schema + var oldVirtualInterfaceNames = oldNetworkConfiguration.VirtualInterfaceNames; + if (oldVirtualInterfaceNames.Equals("vEthernet*", StringComparison.OrdinalIgnoreCase)) + { + networkConfiguration.VirtualInterfaceNames = new string[] { "veth" }; + } + else + { + networkConfiguration.VirtualInterfaceNames = oldVirtualInterfaceNames.Replace("*", string.Empty, StringComparison.OrdinalIgnoreCase).Split(','); + } + + var networkConfigSerializer = new XmlSerializer(typeof(NetworkConfiguration)); + var xmlWriterSettings = new XmlWriterSettings { Indent = true }; + using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings); + networkConfigSerializer.Serialize(xmlWriter, networkConfiguration); + } + +#pragma warning disable + public sealed class OldNetworkConfiguration + { + public const int DefaultHttpPort = 8096; + + public const int DefaultHttpsPort = 8920; + + private string _baseUrl = string.Empty; + + public bool RequireHttps { get; set; } + + public string CertificatePath { get; set; } = string.Empty; + + public string CertificatePassword { get; set; } = string.Empty; + + public string BaseUrl + { + get => _baseUrl; + set + { + // Normalize the start of the string + if (string.IsNullOrWhiteSpace(value)) + { + // If baseUrl is empty, set an empty prefix string + _baseUrl = string.Empty; + return; + } + + if (value[0] != '/') + { + // If baseUrl was not configured with a leading slash, append one for consistency + value = "/" + value; + } + + // Normalize the end of the string + if (value[^1] == '/') + { + // If baseUrl was configured with a trailing slash, remove it for consistency + value = value.Remove(value.Length - 1); + } + + _baseUrl = value; + } + } + + public int PublicHttpsPort { get; set; } = DefaultHttpsPort; + + public int HttpServerPortNumber { get; set; } = DefaultHttpPort; + + public int HttpsPortNumber { get; set; } = DefaultHttpsPort; + + public bool EnableHttps { get; set; } + + public int PublicPort { get; set; } = DefaultHttpPort; + + public bool UPnPCreateHttpPortMap { get; set; } + + public string UDPPortRange { get; set; } = string.Empty; + + public bool EnableIPV6 { get; set; } + + public bool EnableIPV4 { get; set; } = true; + + public bool EnableSSDPTracing { get; set; } + + public string SSDPTracingFilter { get; set; } = string.Empty; + + public int UDPSendCount { get; set; } = 2; + + public int UDPSendDelay { get; set; } = 100; + + public bool IgnoreVirtualInterfaces { get; set; } = true; + + public string VirtualInterfaceNames { get; set; } = "vEthernet*"; + + public int GatewayMonitorPeriod { get; set; } = 60; + + public bool EnableMultiSocketBinding { get; } = true; + + public bool TrustAllIP6Interfaces { get; set; } + + public string HDHomerunPortRange { get; set; } = string.Empty; + + public string[] PublishedServerUriBySubnet { get; set; } = Array.Empty<string>(); + + public bool AutoDiscoveryTracing { get; set; } + + public bool AutoDiscovery { get; set; } = true; + + public string[] RemoteIPFilter { get; set; } = Array.Empty<string>(); + + public bool IsRemoteIPFilterBlacklist { get; set; } + + public bool EnableUPnP { get; set; } + + public bool EnableRemoteAccess { get; set; } = true; + + public string[] LocalNetworkSubnets { get; set; } = Array.Empty<string>(); + + public string[] LocalNetworkAddresses { get; set; } = Array.Empty<string>(); + public string[] KnownProxies { get; set; } = Array.Empty<string>(); + + public bool EnablePublishedServerUriByRequest { get; set; } = false; + } +} diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/RenameEnableGroupingIntoCollections.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/RenameEnableGroupingIntoCollections.cs new file mode 100644 index 00000000..995b2bbf --- /dev/null +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/RenameEnableGroupingIntoCollections.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using Emby.Server.Implementations; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.PreStartupRoutines; + +/// <inheritdoc /> +#pragma warning disable CS0618 // Type or member is obsolete +[JellyfinMigration("2025-04-20T04:00:00", nameof(RenameEnableGroupingIntoCollections), "E73B777D-CD5C-4E71-957A-B86B3660B7CF", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)] +public class RenameEnableGroupingIntoCollections : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete +{ + private readonly ServerApplicationPaths _applicationPaths; + private readonly ILogger<RenameEnableGroupingIntoCollections> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="RenameEnableGroupingIntoCollections"/> class. + /// </summary> + /// <param name="applicationPaths">An instance of <see cref="ServerApplicationPaths"/>.</param> + /// <param name="loggerFactory">An instance of the <see cref="ILoggerFactory"/> interface.</param> + public RenameEnableGroupingIntoCollections(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory) + { + _applicationPaths = applicationPaths; + _logger = loggerFactory.CreateLogger<RenameEnableGroupingIntoCollections>(); + } + + /// <inheritdoc /> + public void Perform() + { + string path = Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "system.xml"); + if (!File.Exists(path)) + { + _logger.LogWarning("Configuration file not found: {Path}", path); + return; + } + + try + { + XDocument xmlDocument = XDocument.Load(path); + var element = xmlDocument.Descendants("EnableGroupingIntoCollections").FirstOrDefault(); + if (element is not null) + { + element.Name = "EnableGroupingMoviesIntoCollections"; + _logger.LogInformation("The tag <EnableGroupingIntoCollections> was successfully renamed to <EnableGroupingMoviesIntoCollections>."); + xmlDocument.Save(path); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occurred while updating the XML file: {Message}", ex.Message); + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/AddDefaultCastReceivers.cs b/Jellyfin.Server/Migrations/Routines/AddDefaultCastReceivers.cs new file mode 100644 index 00000000..00d152b4 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/AddDefaultCastReceivers.cs @@ -0,0 +1,45 @@ +using System; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.System; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Migration to add the default cast receivers to the system config. +/// </summary> +#pragma warning disable CS0618 // Type or member is obsolete +[JellyfinMigration("2025-04-20T16:00:00", nameof(AddDefaultCastReceivers), "34A1A1C4-5572-418E-A2F8-32CDFE2668E8", RunMigrationOnSetup = true)] +public class AddDefaultCastReceivers : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete +{ + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="AddDefaultCastReceivers"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + public AddDefaultCastReceivers(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// <inheritdoc /> + public void Perform() + { + _serverConfigurationManager.Configuration.CastReceiverApplications = + [ + new() + { + Id = "F007D354", + Name = "Stable" + }, + new() + { + Id = "6F511C87", + Name = "Unstable" + } + ]; + + _serverConfigurationManager.SaveConfiguration(); + } +} diff --git a/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs b/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs new file mode 100644 index 00000000..8c8398a1 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs @@ -0,0 +1,39 @@ +using System; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Updates; + +namespace Jellyfin.Server.Migrations.Routines +{ + /// <summary> + /// Migration to initialize system configuration with the default plugin repository. + /// </summary> +#pragma warning disable CS0618 // Type or member is obsolete + [JellyfinMigration("2025-04-20T09:00:00", nameof(AddDefaultPluginRepository), "EB58EBEE-9514-4B9B-8225-12E1A40020DF", RunMigrationOnSetup = true)] + public class AddDefaultPluginRepository : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete + { + private readonly IServerConfigurationManager _serverConfigurationManager; + + private readonly RepositoryInfo _defaultRepositoryInfo = new RepositoryInfo + { + Name = "Jellyfin Stable", + Url = "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" + }; + + /// <summary> + /// Initializes a new instance of the <see cref="AddDefaultPluginRepository"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + public AddDefaultPluginRepository(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// <inheritdoc/> + public void Perform() + { + _serverConfigurationManager.Configuration.PluginRepositories = new[] { _defaultRepositoryInfo }; + _serverConfigurationManager.SaveConfiguration(); + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/CleanMusicArtist.cs b/Jellyfin.Server/Migrations/Routines/CleanMusicArtist.cs new file mode 100644 index 00000000..d5c5f3d9 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/CleanMusicArtist.cs @@ -0,0 +1,47 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations; +using Jellyfin.Server.ServerSetupApp; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Cleans up all Music artists that have been migrated in the 10.11 RC migrations. +/// </summary> +[JellyfinMigration("2025-10-09T20:00:00", nameof(CleanMusicArtist))] +[JellyfinMigrationBackup(JellyfinDb = true)] +public class CleanMusicArtist : IAsyncMigrationRoutine +{ + private readonly IStartupLogger<CleanMusicArtist> _startupLogger; + private readonly IDbContextFactory<JellyfinDbContext> _dbContextFactory; + + /// <summary> + /// Initializes a new instance of the <see cref="CleanMusicArtist"/> class. + /// </summary> + /// <param name="startupLogger">The startup logger.</param> + /// <param name="dbContextFactory">The Db context factory.</param> + public CleanMusicArtist(IStartupLogger<CleanMusicArtist> startupLogger, IDbContextFactory<JellyfinDbContext> dbContextFactory) + { + _startupLogger = startupLogger; + _dbContextFactory = dbContextFactory; + } + + /// <inheritdoc/> + public async Task PerformAsync(CancellationToken cancellationToken) + { + var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + await using (context.ConfigureAwait(false)) + { + var peoples = context.Peoples.Where(e => e.PersonType == nameof(PersonKind.Artist) || e.PersonType == nameof(PersonKind.AlbumArtist)); + _startupLogger.LogInformation("Delete {Number} Artist and Album Artist person types from db", await peoples.CountAsync(cancellationToken).ConfigureAwait(false)); + + await peoples + .ExecuteDeleteAsync(cancellationToken) + .ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs new file mode 100644 index 00000000..1326a6dc --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using MediaBrowser.Common.Configuration; +using Newtonsoft.Json.Linq; + +namespace Jellyfin.Server.Migrations.Routines +{ + /// <summary> + /// Migration to initialize the user logging configuration file "logging.user.json". + /// If the deprecated logging.json file exists and has a custom config, it will be used as logging.user.json, + /// otherwise a blank file will be created. + /// </summary> +#pragma warning disable CS0618 // Type or member is obsolete + [JellyfinMigration("2025-04-20T06:00:00", nameof(CreateUserLoggingConfigFile), "EF103419-8451-40D8-9F34-D1A8E93A1679")] + internal class CreateUserLoggingConfigFile : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete + { + /// <summary> + /// File history for logging.json as existed during this migration creation. The contents for each has been minified. + /// </summary> + private readonly List<string> _defaultConfigHistory = new List<string> + { + // 9a6c27947353585391e211aa88b925f81e8cd7b9 + @"{""Serilog"":{""MinimumLevel"":{""Default"":""Information"",""Override"":{""Microsoft"":""Warning"",""System"":""Warning""}},""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}", + // 71bdcd730705a714ee208eaad7290b7c68df3885 + @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}", + // a44936f97f8afc2817d3491615a7cfe1e31c251c + @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}""}},{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}""}}]}}", + // 7af3754a11ad5a4284f107997fb5419a010ce6f3 + @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}""}}]}}]}}", + // 60691349a11f541958e0b2247c9abc13cb40c9fb + @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}""}}]}}]}}", + // 65fe243afbcc4b596cf8726708c1965cd34b5f68 + @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {ThreadId} {SourceContext}: {Message:lj} {NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {ThreadId} {SourceContext}:{Message} {NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}", + // 96c9af590494aa8137d5a061aaf1e68feee60b67 + @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}:{Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}", + }; + + private readonly IApplicationPaths _appPaths; + + public CreateUserLoggingConfigFile(IApplicationPaths appPaths) + { + _appPaths = appPaths; + } + + /// <inheritdoc/> + public void Perform() + { + var logDirectory = _appPaths.ConfigurationDirectoryPath; + var existingConfigPath = Path.Combine(logDirectory, "logging.json"); + + // If the existing logging.json config file is unmodified, then 'reset' it by moving it to 'logging.old.json' + // NOTE: This config file has 'reloadOnChange: true', so this change will take effect immediately even though it has already been loaded + if (File.Exists(existingConfigPath) && ExistingConfigUnmodified(existingConfigPath)) + { + File.Move(existingConfigPath, Path.Combine(logDirectory, "logging.old.json")); + } + } + + /// <summary> + /// Check if the existing logging.json file has not been modified by the user by comparing it to all the + /// versions in our git history. Until now, the file has never been migrated after first creation so users + /// could have any version from the git history. + /// </summary> + /// <exception cref="IOException"><paramref name="oldConfigPath"/> does not exist or could not be read.</exception> + private bool ExistingConfigUnmodified(string oldConfigPath) + { + var existingConfigJson = JToken.Parse(File.ReadAllText(oldConfigPath)); + return _defaultConfigHistory + .Select(JToken.Parse) + .Any(historicalConfigJson => JToken.DeepEquals(existingConfigJson, historicalConfigJson)); + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/DisableLegacyAuthorization.cs b/Jellyfin.Server/Migrations/Routines/DisableLegacyAuthorization.cs new file mode 100644 index 00000000..6edfcbcf --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/DisableLegacyAuthorization.cs @@ -0,0 +1,32 @@ +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Configuration; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Migration to disable legacy authorization in the system config. +/// </summary> +[JellyfinMigration("2025-11-18T16:00:00", nameof(DisableLegacyAuthorization))] +public class DisableLegacyAuthorization : IAsyncMigrationRoutine +{ + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="DisableLegacyAuthorization"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + public DisableLegacyAuthorization(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// <inheritdoc /> + public Task PerformAsync(CancellationToken cancellationToken) + { + _serverConfigurationManager.Configuration.EnableLegacyAuthorization = false; + _serverConfigurationManager.SaveConfiguration(); + + return Task.CompletedTask; + } +} diff --git a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs new file mode 100644 index 00000000..acf2835f --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs @@ -0,0 +1,38 @@ +using System; +using MediaBrowser.Common.Configuration; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines +{ + /// <summary> + /// Disable transcode throttling for all installations since it is currently broken for certain video formats. + /// </summary> +#pragma warning disable CS0618 // Type or member is obsolete + [JellyfinMigration("2025-04-20T05:00:00", nameof(DisableTranscodingThrottling), "4124C2CD-E939-4FFB-9BE9-9B311C413638")] + internal class DisableTranscodingThrottling : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete + { + private readonly ILogger<DisableTranscodingThrottling> _logger; + private readonly IConfigurationManager _configManager; + + public DisableTranscodingThrottling(ILogger<DisableTranscodingThrottling> logger, IConfigurationManager configManager) + { + _logger = logger; + _configManager = configManager; + } + + /// <inheritdoc/> + public void Perform() + { + // Set EnableThrottling to false since it wasn't used before and may introduce issues + var encoding = _configManager.GetEncodingOptions(); + if (encoding.EnableThrottling) + { + _logger.LogInformation("Disabling transcoding throttling during migration"); + encoding.EnableThrottling = false; + + _configManager.SaveConfiguration("encoding", encoding); + } + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/FixAudioData.cs b/Jellyfin.Server/Migrations/Routines/FixAudioData.cs new file mode 100644 index 00000000..05ded06b --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/FixAudioData.cs @@ -0,0 +1,77 @@ +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines +{ + /// <summary> + /// Fixes the data column of audio types to be deserializable. + /// </summary> +#pragma warning disable CS0618 // Type or member is obsolete + [JellyfinMigration("2025-04-20T18:00:00", nameof(FixAudioData), "CF6FABC2-9FBE-4933-84A5-FFE52EF22A58")] + [JellyfinMigrationBackup(LegacyLibraryDb = true)] + internal class FixAudioData : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete + { + private readonly ILogger<FixAudioData> _logger; + private readonly IServerApplicationPaths _applicationPaths; + private readonly IItemRepository _itemRepository; + + public FixAudioData( + IServerApplicationPaths applicationPaths, + ILoggerFactory loggerFactory, + IItemRepository itemRepository) + { + _applicationPaths = applicationPaths; + _itemRepository = itemRepository; + _logger = loggerFactory.CreateLogger<FixAudioData>(); + } + + /// <inheritdoc/> + public void Perform() + { + _logger.LogInformation("Backfilling audio lyrics data to database."); + var startIndex = 0; + var records = _itemRepository.GetCount(new InternalItemsQuery + { + IncludeItemTypes = [BaseItemKind.Audio], + }); + + while (startIndex < records) + { + var results = _itemRepository.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = [BaseItemKind.Audio], + StartIndex = startIndex, + Limit = 5000, + SkipDeserialization = true + }) + .Cast<Audio>() + .ToList(); + + foreach (var audio in results) + { + var lyricMediaStreams = audio.GetMediaStreams().Where(s => s.Type == MediaStreamType.Lyric).Select(s => s.Path).ToList(); + if (lyricMediaStreams.Count > 0) + { + audio.HasLyrics = true; + audio.LyricFiles = lyricMediaStreams; + } + } + + _itemRepository.SaveItems(results, CancellationToken.None); + startIndex += results.Count; + _logger.LogInformation("Backfilled data for {UpdatedRecords} of {TotalRecords} audio records", startIndex, records); + } + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/FixDates.cs b/Jellyfin.Server/Migrations/Routines/FixDates.cs new file mode 100644 index 00000000..a5b11b11 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/FixDates.cs @@ -0,0 +1,171 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations; +using Jellyfin.Server.ServerSetupApp; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Migration to fix dates saved in the database to always be UTC. +/// </summary> +[JellyfinMigration("2025-06-20T18:00:00", nameof(FixDates))] +public class FixDates : IAsyncMigrationRoutine +{ + private const int PageSize = 5000; + + private readonly ILogger _logger; + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + + /// <summary> + /// Initializes a new instance of the <see cref="FixDates"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="startupLogger">The startup logger for Startup UI integration.</param> + /// <param name="dbProvider">Instance of the <see cref="IDbContextFactory{JellyfinDbContext}"/> interface.</param> + public FixDates( + ILogger<FixDates> logger, + IStartupLogger<FixDates> startupLogger, + IDbContextFactory<JellyfinDbContext> dbProvider) + { + _logger = startupLogger.With(logger); + _dbProvider = dbProvider; + } + + /// <inheritdoc /> + public async Task PerformAsync(CancellationToken cancellationToken) + { + if (!TimeZoneInfo.Local.Equals(TimeZoneInfo.Utc)) + { + var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + await using (context.ConfigureAwait(false)) + { + var sw = Stopwatch.StartNew(); + + await FixBaseItemsAsync(context, sw, cancellationToken).ConfigureAwait(false); + sw.Reset(); + await FixChaptersAsync(context, sw, cancellationToken).ConfigureAwait(false); + sw.Reset(); + await FixBaseItemImageInfos(context, sw, cancellationToken).ConfigureAwait(false); + } + } + } + + private async Task FixBaseItemsAsync(JellyfinDbContext context, Stopwatch sw, CancellationToken cancellationToken) + { + int itemCount = 0; + + var baseQuery = context.BaseItems.OrderBy(e => e.Id); + var records = baseQuery.Count(); + _logger.LogInformation("Fixing dates for {Count} BaseItems.", records); + + sw.Start(); + await foreach (var result in context.BaseItems.OrderBy(e => e.Id) + .WithPartitionProgress( + (partition) => + _logger.LogInformation( + "Processing BaseItems batch {BatchNumber} ({ProcessedSoFar}/{TotalRecords}) - Time: {ElapsedTime}", + partition + 1, + Math.Min((partition + 1) * PageSize, records), + records, + sw.Elapsed)) + .PartitionEagerAsync(PageSize, cancellationToken) + .WithCancellation(cancellationToken) + .ConfigureAwait(false)) + { + result.DateCreated = ToUniversalTime(result.DateCreated); + result.DateLastMediaAdded = ToUniversalTime(result.DateLastMediaAdded); + result.DateLastRefreshed = ToUniversalTime(result.DateLastRefreshed); + result.DateLastSaved = ToUniversalTime(result.DateLastSaved); + result.DateModified = ToUniversalTime(result.DateModified); + itemCount++; + } + + var saveCount = await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + _logger.LogInformation("BaseItems: Processed {ItemCount} items, saved {SaveCount} changes in {ElapsedTime}", itemCount, saveCount, sw.Elapsed); + } + + private async Task FixChaptersAsync(JellyfinDbContext context, Stopwatch sw, CancellationToken cancellationToken) + { + int itemCount = 0; + + var baseQuery = context.Chapters; + var records = baseQuery.Count(); + _logger.LogInformation("Fixing dates for {Count} Chapters.", records); + + sw.Start(); + await foreach (var result in context.Chapters.OrderBy(e => e.ItemId) + .WithPartitionProgress( + (partition) => + _logger.LogInformation( + "Processing Chapter batch {BatchNumber} ({ProcessedSoFar}/{TotalRecords}) - Time: {ElapsedTime}", + partition + 1, + Math.Min((partition + 1) * PageSize, records), + records, + sw.Elapsed)) + .PartitionEagerAsync(PageSize, cancellationToken) + .WithCancellation(cancellationToken) + .ConfigureAwait(false)) + { + result.ImageDateModified = ToUniversalTime(result.ImageDateModified, true); + itemCount++; + } + + var saveCount = await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + _logger.LogInformation("Chapters: Processed {ItemCount} items, saved {SaveCount} changes in {ElapsedTime}", itemCount, saveCount, sw.Elapsed); + } + + private async Task FixBaseItemImageInfos(JellyfinDbContext context, Stopwatch sw, CancellationToken cancellationToken) + { + int itemCount = 0; + + var baseQuery = context.BaseItemImageInfos; + var records = baseQuery.Count(); + _logger.LogInformation("Fixing dates for {Count} BaseItemImageInfos.", records); + + sw.Start(); + await foreach (var result in context.BaseItemImageInfos.OrderBy(e => e.Id) + .WithPartitionProgress( + (partition) => + _logger.LogInformation( + "Processing BaseItemImageInfos batch {BatchNumber} ({ProcessedSoFar}/{TotalRecords}) - Time: {ElapsedTime}", + partition + 1, + Math.Min((partition + 1) * PageSize, records), + records, + sw.Elapsed)) + .PartitionEagerAsync(PageSize, cancellationToken) + .WithCancellation(cancellationToken) + .ConfigureAwait(false)) + { + result.DateModified = ToUniversalTime(result.DateModified); + itemCount++; + } + + var saveCount = await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + _logger.LogInformation("BaseItemImageInfos: Processed {ItemCount} items, saved {SaveCount} changes in {ElapsedTime}", itemCount, saveCount, sw.Elapsed); + } + + private DateTime? ToUniversalTime(DateTime? dateTime, bool isUTC = false) + { + if (dateTime is null) + { + return null; + } + + if (dateTime.Value.Year == 1 && dateTime.Value.Month == 1 && dateTime.Value.Day == 1) + { + return null; + } + + if (dateTime.Value.Kind == DateTimeKind.Utc || isUTC) + { + return dateTime.Value; + } + + return dateTime.Value.ToUniversalTime(); + } +} diff --git a/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs b/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs new file mode 100644 index 00000000..56614ece --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs @@ -0,0 +1,70 @@ +using System; +using System.Linq; +using System.Threading; + +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Properly set playlist owner. +/// </summary> +#pragma warning disable CS0618 // Type or member is obsolete +[JellyfinMigration("2025-04-20T15:00:00", nameof(FixPlaylistOwner), "615DFA9E-2497-4DBB-A472-61938B752C5B")] +internal class FixPlaylistOwner : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete +{ + private readonly ILogger<FixPlaylistOwner> _logger; + private readonly ILibraryManager _libraryManager; + private readonly IPlaylistManager _playlistManager; + + public FixPlaylistOwner( + ILogger<FixPlaylistOwner> logger, + ILibraryManager libraryManager, + IPlaylistManager playlistManager) + { + _logger = logger; + _libraryManager = libraryManager; + _playlistManager = playlistManager; + } + + /// <inheritdoc/> + public void Perform() + { + var playlists = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new[] { BaseItemKind.Playlist } + }) + .Cast<Playlist>() + .Where(x => x.OwnerUserId.Equals(Guid.Empty)) + .ToArray(); + + if (playlists.Length > 0) + { + foreach (var playlist in playlists) + { + var shares = playlist.Shares; + if (shares.Count > 0) + { + var firstEditShare = shares.First(x => x.CanEdit); + if (firstEditShare is not null) + { + playlist.OwnerUserId = firstEditShare.UserId; + playlist.Shares = shares.Where(x => x != firstEditShare).ToArray(); + playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult(); + _playlistManager.SavePlaylistFile(playlist); + } + } + else + { + playlist.OpenAccess = true; + playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult(); + } + } + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs new file mode 100644 index 00000000..8c856319 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Emby.Server.Implementations.Data; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines +{ + /// <summary> + /// The migration routine for migrating the activity log database to EF Core. + /// </summary> +#pragma warning disable CS0618 // Type or member is obsolete + [JellyfinMigration("2025-04-20T07:00:00", nameof(MigrateActivityLogDb), "3793eb59-bc8c-456c-8b9f-bd5a62a42978")] + public class MigrateActivityLogDb : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete + { + private const string DbFilename = "activitylog.db"; + + private readonly ILogger<MigrateActivityLogDb> _logger; + private readonly IDbContextFactory<JellyfinDbContext> _provider; + private readonly IServerApplicationPaths _paths; + + /// <summary> + /// Initializes a new instance of the <see cref="MigrateActivityLogDb"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="paths">The server application paths.</param> + /// <param name="provider">The database provider.</param> + public MigrateActivityLogDb(ILogger<MigrateActivityLogDb> logger, IServerApplicationPaths paths, IDbContextFactory<JellyfinDbContext> provider) + { + _logger = logger; + _provider = provider; + _paths = paths; + } + + /// <inheritdoc/> + public void Perform() + { + var logLevelDictionary = new Dictionary<string, LogLevel>(StringComparer.OrdinalIgnoreCase) + { + { "None", LogLevel.None }, + { "Trace", LogLevel.Trace }, + { "Debug", LogLevel.Debug }, + { "Information", LogLevel.Information }, + { "Info", LogLevel.Information }, + { "Warn", LogLevel.Warning }, + { "Warning", LogLevel.Warning }, + { "Error", LogLevel.Error }, + { "Critical", LogLevel.Critical } + }; + + var dataPath = _paths.DataPath; + var activityLogPath = Path.Combine(dataPath, DbFilename); + if (!File.Exists(activityLogPath)) + { + _logger.LogWarning("{ActivityLogDb} doesn't exist, nothing to migrate", activityLogPath); + return; + } + + using (var connection = new SqliteConnection($"Filename={activityLogPath}")) + { + connection.Open(); + var tableQuery = connection.Query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='ActivityLog';"); + foreach (var row in tableQuery) + { + if (row.GetInt32(0) == 0) + { + _logger.LogWarning("Table 'ActivityLog' doesn't exist in {ActivityLogPath}, nothing to migrate", activityLogPath); + return; + } + } + + using var userDbConnection = new SqliteConnection($"Filename={Path.Combine(dataPath, "users.db")}"); + userDbConnection.Open(); + _logger.LogWarning("Migrating the activity database may take a while, do not stop Jellyfin."); + using var dbContext = _provider.CreateDbContext(); + + // Make sure that the database is empty in case of failed migration due to power outages, etc. + dbContext.ActivityLogs.RemoveRange(dbContext.ActivityLogs); + dbContext.SaveChanges(); + // Reset the autoincrement counter + dbContext.Database.ExecuteSqlRaw("UPDATE sqlite_sequence SET seq = 0 WHERE name = 'ActivityLog';"); + dbContext.SaveChanges(); + + var newEntries = new List<ActivityLog>(); + + var queryResult = connection.Query("SELECT * FROM ActivityLog ORDER BY Id"); + + foreach (var entry in queryResult) + { + if (!logLevelDictionary.TryGetValue(entry.GetString(8), out var severity)) + { + severity = LogLevel.Trace; + } + + var guid = Guid.Empty; + if (!entry.IsDBNull(6) && !entry.TryGetGuid(6, out guid)) + { + var id = entry.GetString(6); + // This is not a valid Guid, see if it is an internal ID from an old Emby schema + _logger.LogWarning("Invalid Guid in UserId column: {Guid}", id); + + using var statement = userDbConnection.PrepareStatement("SELECT guid FROM LocalUsersv2 WHERE Id=@Id"); + statement.TryBind("@Id", id); + + using var reader = statement.ExecuteReader(); + if (reader.HasRows && reader.Read() && reader.TryGetGuid(0, out guid)) + { + // Successfully parsed a Guid from the user table. + break; + } + } + + var newEntry = new ActivityLog(entry.GetString(1), entry.GetString(4), guid) + { + DateCreated = entry.GetDateTime(7), + LogSeverity = severity + }; + + if (entry.TryGetString(2, out var result)) + { + newEntry.Overview = result; + } + + if (entry.TryGetString(3, out result)) + { + newEntry.ShortOverview = result; + } + + if (entry.TryGetString(5, out result)) + { + newEntry.ItemId = result; + } + + newEntries.Add(newEntry); + } + + dbContext.ActivityLogs.AddRange(newEntries); + dbContext.SaveChanges(); + } + + try + { + File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old")); + + var journalPath = Path.Combine(dataPath, DbFilename + "-journal"); + if (File.Exists(journalPath)) + { + File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal")); + } + } + catch (IOException e) + { + _logger.LogError(e, "Error renaming legacy activity log database to 'activitylog.db.old'"); + } + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs new file mode 100644 index 00000000..0de775e0 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Emby.Server.Implementations.Data; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities.Security; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Library; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines +{ + /// <summary> + /// A migration that moves data from the authentication database into the new schema. + /// </summary> +#pragma warning disable CS0618 // Type or member is obsolete + [JellyfinMigration("2025-04-20T14:00:00", nameof(MigrateAuthenticationDb), "5BD72F41-E6F3-4F60-90AA-09869ABE0E22")] + public class MigrateAuthenticationDb : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete + { + private const string DbFilename = "authentication.db"; + + private readonly ILogger<MigrateAuthenticationDb> _logger; + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + private readonly IServerApplicationPaths _appPaths; + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="MigrateAuthenticationDb"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="dbProvider">The database provider.</param> + /// <param name="appPaths">The server application paths.</param> + /// <param name="userManager">The user manager.</param> + public MigrateAuthenticationDb( + ILogger<MigrateAuthenticationDb> logger, + IDbContextFactory<JellyfinDbContext> dbProvider, + IServerApplicationPaths appPaths, + IUserManager userManager) + { + _logger = logger; + _dbProvider = dbProvider; + _appPaths = appPaths; + _userManager = userManager; + } + + /// <inheritdoc /> + public void Perform() + { + var dataPath = _appPaths.DataPath; + var dbFilePath = Path.Combine(dataPath, DbFilename); + + if (!File.Exists(dbFilePath)) + { + _logger.LogWarning("{Path} doesn't exist, nothing to migrate", dbFilePath); + return; + } + + using (var connection = new SqliteConnection($"Filename={dbFilePath}")) + { + connection.Open(); + + var tableQuery = connection.Query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='Tokens';"); + foreach (var row in tableQuery) + { + if (row.GetInt32(0) == 0) + { + _logger.LogWarning("Table 'Tokens' doesn't exist in {Path}, nothing to migrate", dbFilePath); + return; + } + } + + using var dbContext = _dbProvider.CreateDbContext(); + + var authenticatedDevices = connection.Query("SELECT * FROM Tokens"); + + foreach (var row in authenticatedDevices) + { + var dateCreatedStr = row.GetString(9); + _ = DateTime.TryParse(dateCreatedStr, out var dateCreated); + var dateLastActivityStr = row.GetString(10); + _ = DateTime.TryParse(dateLastActivityStr, out var dateLastActivity); + + if (row.IsDBNull(6)) + { + dbContext.ApiKeys.Add(new ApiKey(row.GetString(3)) + { + AccessToken = row.GetString(1), + DateCreated = dateCreated, + DateLastActivity = dateLastActivity + }); + } + else + { + var userId = row.GetGuid(6); + var user = _userManager.GetUserById(userId); + if (user is null) + { + // User doesn't exist, don't bring over the device. + continue; + } + + dbContext.Devices.Add(new Device( + userId, + row.GetString(3), + row.GetString(4), + row.GetString(5), + row.GetString(2)) + { + AccessToken = row.GetString(1), + IsActive = row.GetBoolean(8), + DateCreated = dateCreated, + DateLastActivity = dateLastActivity + }); + } + } + + var deviceOptions = connection.Query("SELECT * FROM Devices"); + var deviceIds = new HashSet<string>(); + foreach (var row in deviceOptions) + { + if (row.IsDBNull(2)) + { + continue; + } + + var deviceId = row.GetString(2); + if (deviceIds.Contains(deviceId)) + { + continue; + } + + deviceIds.Add(deviceId); + + dbContext.DeviceOptions.Add(new DeviceOptions(deviceId) + { + CustomName = row.IsDBNull(1) ? null : row.GetString(1) + }); + } + + dbContext.SaveChanges(); + } + + try + { + File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old")); + + var journalPath = Path.Combine(dataPath, DbFilename + "-journal"); + if (File.Exists(journalPath)) + { + File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal")); + } + } + catch (IOException e) + { + _logger.LogError(e, "Error renaming legacy activity log database to 'authentication.db.old'"); + } + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs new file mode 100644 index 00000000..ffd06fea --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using Emby.Server.Implementations.Data; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dto; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines +{ + /// <summary> + /// The migration routine for migrating the display preferences database to EF Core. + /// </summary> +#pragma warning disable CS0618 // Type or member is obsolete + [JellyfinMigration("2025-04-20T12:00:00", nameof(MigrateDisplayPreferencesDb), "06387815-C3CC-421F-A888-FB5F9992BEA8")] + public class MigrateDisplayPreferencesDb : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete + { + private const string DbFilename = "displaypreferences.db"; + + private readonly ILogger<MigrateDisplayPreferencesDb> _logger; + private readonly IServerApplicationPaths _paths; + private readonly IDbContextFactory<JellyfinDbContext> _provider; + private readonly JsonSerializerOptions _jsonOptions; + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="MigrateDisplayPreferencesDb"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="paths">The server application paths.</param> + /// <param name="provider">The database provider.</param> + /// <param name="userManager">The user manager.</param> + public MigrateDisplayPreferencesDb( + ILogger<MigrateDisplayPreferencesDb> logger, + IServerApplicationPaths paths, + IDbContextFactory<JellyfinDbContext> provider, + IUserManager userManager) + { + _logger = logger; + _paths = paths; + _provider = provider; + _userManager = userManager; + _jsonOptions = new JsonSerializerOptions(); + _jsonOptions.Converters.Add(new JsonStringEnumConverter()); + } + + /// <inheritdoc /> + public void Perform() + { + HomeSectionType[] defaults = + { + HomeSectionType.SmallLibraryTiles, + HomeSectionType.Resume, + HomeSectionType.ResumeAudio, + HomeSectionType.LiveTv, + HomeSectionType.NextUp, + HomeSectionType.LatestMedia, + HomeSectionType.None, + }; + + var chromecastDict = new Dictionary<string, ChromecastVersion>(StringComparer.OrdinalIgnoreCase) + { + { "stable", ChromecastVersion.Stable }, + { "nightly", ChromecastVersion.Unstable }, + { "unstable", ChromecastVersion.Unstable } + }; + + var displayPrefs = new HashSet<string>(StringComparer.OrdinalIgnoreCase); + var customDisplayPrefs = new HashSet<string>(StringComparer.OrdinalIgnoreCase); + var dbFilePath = Path.Combine(_paths.DataPath, DbFilename); + + if (!File.Exists(dbFilePath)) + { + _logger.LogWarning("{Path} doesn't exist, nothing to migrate", dbFilePath); + return; + } + + using (var connection = new SqliteConnection($"Filename={dbFilePath}")) + { + connection.Open(); + + var tableQuery = connection.Query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='userdisplaypreferences';"); + foreach (var row in tableQuery) + { + if (row.GetInt32(0) == 0) + { + _logger.LogWarning("Table 'userdisplaypreferences' doesn't exist in {Path}, nothing to migrate", dbFilePath); + return; + } + } + + using var dbContext = _provider.CreateDbContext(); + + var results = connection.Query("SELECT * FROM userdisplaypreferences"); + foreach (var result in results) + { + var dto = JsonSerializer.Deserialize<DisplayPreferencesDto>(result.GetStream(3), _jsonOptions); + if (dto is null) + { + continue; + } + + var itemId = result.GetGuid(1); + var dtoUserId = itemId; + var client = result.GetString(2); + var displayPreferencesKey = $"{dtoUserId}|{itemId}|{client}"; + if (displayPrefs.Contains(displayPreferencesKey)) + { + // Duplicate display preference. + continue; + } + + displayPrefs.Add(displayPreferencesKey); + var existingUser = _userManager.GetUserById(dtoUserId); + if (existingUser is null) + { + _logger.LogWarning("User with ID {UserId} does not exist in the database, skipping migration.", dtoUserId); + continue; + } + + var chromecastVersion = dto.CustomPrefs.TryGetValue("chromecastVersion", out var version) + && !string.IsNullOrEmpty(version) + ? chromecastDict[version] + : ChromecastVersion.Stable; + dto.CustomPrefs.Remove("chromecastVersion"); + + var displayPreferences = new DisplayPreferences(dtoUserId, itemId, client) + { + IndexBy = Enum.TryParse<IndexingKind>(dto.IndexBy, true, out var indexBy) ? indexBy : null, + ShowBackdrop = dto.ShowBackdrop, + ShowSidebar = dto.ShowSidebar, + ScrollDirection = dto.ScrollDirection, + ChromecastVersion = chromecastVersion, + SkipForwardLength = dto.CustomPrefs.TryGetValue("skipForwardLength", out var length) && int.TryParse(length, out var skipForwardLength) + ? skipForwardLength + : 30000, + SkipBackwardLength = dto.CustomPrefs.TryGetValue("skipBackLength", out length) && int.TryParse(length, out var skipBackwardLength) + ? skipBackwardLength + : 10000, + EnableNextVideoInfoOverlay = !dto.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enabled) || string.IsNullOrEmpty(enabled) || bool.Parse(enabled), + DashboardTheme = dto.CustomPrefs.TryGetValue("dashboardtheme", out var theme) ? theme : string.Empty, + TvHome = dto.CustomPrefs.TryGetValue("tvhome", out var home) ? home : string.Empty + }; + + dto.CustomPrefs.Remove("skipForwardLength"); + dto.CustomPrefs.Remove("skipBackLength"); + dto.CustomPrefs.Remove("enableNextVideoInfoOverlay"); + dto.CustomPrefs.Remove("dashboardtheme"); + dto.CustomPrefs.Remove("tvhome"); + + for (int i = 0; i < 7; i++) + { + var key = "homesection" + i; + dto.CustomPrefs.TryGetValue(key, out var homeSection); + + displayPreferences.HomeSections.Add(new HomeSection + { + Order = i, + Type = Enum.TryParse<HomeSectionType>(homeSection, true, out var type) ? type : defaults[i] + }); + + dto.CustomPrefs.Remove(key); + } + + var defaultLibraryPrefs = new ItemDisplayPreferences(displayPreferences.UserId, Guid.Empty, displayPreferences.Client) + { + SortBy = dto.SortBy ?? "SortName", + SortOrder = dto.SortOrder, + RememberIndexing = dto.RememberIndexing, + RememberSorting = dto.RememberSorting, + }; + + dbContext.Add(defaultLibraryPrefs); + + foreach (var key in dto.CustomPrefs.Keys.Where(key => key.StartsWith("landing-", StringComparison.Ordinal))) + { + if (!Guid.TryParse(key.AsSpan().Slice("landing-".Length), out var landingItemId)) + { + continue; + } + + var libraryDisplayPreferences = new ItemDisplayPreferences(displayPreferences.UserId, landingItemId, displayPreferences.Client) + { + SortBy = dto.SortBy ?? "SortName", + SortOrder = dto.SortOrder, + RememberIndexing = dto.RememberIndexing, + RememberSorting = dto.RememberSorting, + }; + + if (Enum.TryParse<ViewType>(dto.ViewType, true, out var viewType)) + { + libraryDisplayPreferences.ViewType = viewType; + } + + dto.CustomPrefs.Remove(key); + dbContext.ItemDisplayPreferences.Add(libraryDisplayPreferences); + } + + foreach (var (key, value) in dto.CustomPrefs) + { + // Custom display preferences can have a key collision. + var indexKey = $"{displayPreferences.UserId}|{itemId}|{displayPreferences.Client}|{key}"; + if (!customDisplayPrefs.Contains(indexKey)) + { + dbContext.Add(new CustomItemDisplayPreferences(displayPreferences.UserId, itemId, displayPreferences.Client, key, value)); + customDisplayPrefs.Add(indexKey); + } + } + + dbContext.Add(displayPreferences); + } + + dbContext.SaveChanges(); + } + + try + { + File.Move(dbFilePath, dbFilePath + ".old"); + + var journalPath = dbFilePath + "-journal"; + if (File.Exists(journalPath)) + { + File.Move(journalPath, dbFilePath + ".old-journal"); + } + } + catch (IOException e) + { + _logger.LogError(e, "Error renaming legacy display preferences database to 'displaypreferences.db.old'"); + } + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs b/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs new file mode 100644 index 00000000..aa553092 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs @@ -0,0 +1,169 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.Json; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Extensions.Json; +using Jellyfin.Server.ServerSetupApp; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Migration to move extracted files to the new directories. +/// </summary> +[JellyfinMigration("2025-04-21T00:00:00", nameof(MigrateKeyframeData))] +public class MigrateKeyframeData : IDatabaseMigrationRoutine +{ + private readonly IStartupLogger _logger; + private readonly IApplicationPaths _appPaths; + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + private static readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + + /// <summary> + /// Initializes a new instance of the <see cref="MigrateKeyframeData"/> class. + /// </summary> + /// <param name="startupLogger">The startup logger for Startup UI intigration.</param> + /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param> + /// <param name="dbProvider">The EFCore db factory.</param> + public MigrateKeyframeData( + IStartupLogger<MigrateKeyframeData> startupLogger, + IApplicationPaths appPaths, + IDbContextFactory<JellyfinDbContext> dbProvider) + { + _logger = startupLogger; + _appPaths = appPaths; + _dbProvider = dbProvider; + } + + private string KeyframeCachePath => Path.Combine(_appPaths.DataPath, "keyframes"); + + /// <inheritdoc /> + public void Perform() + { + const int Limit = 5000; + int itemCount = 0, offset = 0; + + var sw = Stopwatch.StartNew(); + + using var context = _dbProvider.CreateDbContext(); + var baseQuery = context.BaseItems.Where(b => b.MediaType == MediaType.Video.ToString() && !b.IsVirtualItem && !b.IsFolder).OrderBy(e => e.Id); + var records = baseQuery.Count(); + _logger.LogInformation("Checking {Count} items for importable keyframe data.", records); + + context.KeyframeData.ExecuteDelete(); + using var transaction = context.Database.BeginTransaction(); + do + { + var results = baseQuery.Skip(offset).Take(Limit).Select(b => new Tuple<Guid, string?>(b.Id, b.Path)).ToList(); + foreach (var result in results) + { + if (TryGetKeyframeData(result.Item1, result.Item2, out var data)) + { + itemCount++; + context.KeyframeData.Add(data); + } + } + + offset += Limit; + if (offset > records) + { + offset = records; + } + + _logger.LogInformation("Checked: {Count} - Imported: {Items} - Time: {Time}", offset, itemCount, sw.Elapsed); + } while (offset < records); + + context.SaveChanges(); + transaction.Commit(); + + _logger.LogInformation("Imported keyframes for {Count} items in {Time}", itemCount, sw.Elapsed); + + if (Directory.Exists(KeyframeCachePath)) + { + Directory.Delete(KeyframeCachePath, true); + } + } + + private bool TryGetKeyframeData(Guid id, string? path, [NotNullWhen(true)] out KeyframeData? data) + { + data = null; + if (!string.IsNullOrEmpty(path)) + { + var cachePath = GetCachePath(KeyframeCachePath, path); + if (TryReadFromCache(cachePath, out var keyframeData)) + { + data = new() + { + ItemId = id, + KeyframeTicks = keyframeData.KeyframeTicks.ToList(), + TotalDuration = keyframeData.TotalDuration + }; + + return true; + } + } + + return false; + } + + private string? GetCachePath(string keyframeCachePath, string filePath) + { + DateTime? lastWriteTimeUtc; + try + { + lastWriteTimeUtc = File.GetLastWriteTimeUtc(filePath); + } + catch (ArgumentOutOfRangeException e) + { + _logger.LogDebug("Skipping {Path}: {Exception}", filePath, e.Message); + return null; + } + catch (UnauthorizedAccessException e) + { + _logger.LogDebug("Skipping {Path}: {Exception}", filePath, e.Message); + return null; + } + catch (IOException e) + { + _logger.LogDebug("Skipping {Path}: {Exception}", filePath, e.Message); + + return null; + } + + ReadOnlySpan<char> filename = (filePath + "_" + lastWriteTimeUtc.Value.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5() + ".json"; + var prefix = filename[..1]; + + return Path.Join(keyframeCachePath, prefix, filename); + } + + private bool TryReadFromCache(string? cachePath, [NotNullWhen(true)] out MediaEncoding.Keyframes.KeyframeData? cachedResult) + { + if (File.Exists(cachePath)) + { + try + { + var bytes = File.ReadAllBytes(cachePath); + cachedResult = JsonSerializer.Deserialize<MediaEncoding.Keyframes.KeyframeData>(bytes, _jsonOptions); + + return cachedResult is not null; + } + catch (JsonException jsonException) + { + _logger.LogWarning(jsonException, "Failed to read {Path}", cachePath); + } + } + + cachedResult = null; + + return false; + } +} diff --git a/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs new file mode 100644 index 00000000..70761fa7 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs @@ -0,0 +1,1472 @@ +#pragma warning disable RS0030 // Do not use banned APIs + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Data; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using Emby.Server.Implementations.Data; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Extensions; +using Jellyfin.Server.Implementations.Item; +using Jellyfin.Server.ServerSetupApp; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using BaseItemEntity = Jellyfin.Database.Implementations.Entities.BaseItemEntity; +using Chapter = Jellyfin.Database.Implementations.Entities.Chapter; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// The migration routine for migrating the userdata database to EF Core. +/// </summary> +[JellyfinMigration("2025-04-20T20:00:00", nameof(MigrateLibraryDb))] +[JellyfinMigrationBackup(JellyfinDb = true, LegacyLibraryDb = true)] +internal class MigrateLibraryDb : IDatabaseMigrationRoutine +{ + private const string DbFilename = "library.db"; + + private readonly IStartupLogger _logger; + private readonly IServerApplicationPaths _paths; + private readonly IJellyfinDatabaseProvider _jellyfinDatabaseProvider; + private readonly IDbContextFactory<JellyfinDbContext> _provider; + + /// <summary> + /// Initializes a new instance of the <see cref="MigrateLibraryDb"/> class. + /// </summary> + /// <param name="startupLogger">The startup logger for Startup UI intigration.</param> + /// <param name="provider">The database provider.</param> + /// <param name="paths">The server application paths.</param> + /// <param name="jellyfinDatabaseProvider">The database provider for special access.</param> + public MigrateLibraryDb( + IStartupLogger<MigrateLibraryDb> startupLogger, + IDbContextFactory<JellyfinDbContext> provider, + IServerApplicationPaths paths, + IJellyfinDatabaseProvider jellyfinDatabaseProvider) + { + _logger = startupLogger; + _provider = provider; + _paths = paths; + _jellyfinDatabaseProvider = jellyfinDatabaseProvider; + } + + /// <inheritdoc/> + public void Perform() + { + _logger.LogInformation("Migrating the userdata from library.db may take a while, do not stop Jellyfin."); + + var dataPath = _paths.DataPath; + var libraryDbPath = Path.Combine(dataPath, DbFilename); + if (!File.Exists(libraryDbPath)) + { + _logger.LogError("Cannot migrate {LibraryDb} as it does not exist..", libraryDbPath); + return; + } + + using var connection = new SqliteConnection($"Filename={libraryDbPath};Mode=ReadOnly"); + + var fullOperationTimer = new Stopwatch(); + fullOperationTimer.Start(); + + using (var operation = GetPreparedDbContext("Cleanup database")) + { + operation.JellyfinDbContext.AttachmentStreamInfos.ExecuteDelete(); + operation.JellyfinDbContext.BaseItems.ExecuteDelete(); + operation.JellyfinDbContext.ItemValues.ExecuteDelete(); + operation.JellyfinDbContext.UserData.ExecuteDelete(); + operation.JellyfinDbContext.MediaStreamInfos.ExecuteDelete(); + operation.JellyfinDbContext.Peoples.ExecuteDelete(); + operation.JellyfinDbContext.PeopleBaseItemMap.ExecuteDelete(); + operation.JellyfinDbContext.Chapters.ExecuteDelete(); + operation.JellyfinDbContext.AncestorIds.ExecuteDelete(); + } + + // notify the other migration to just silently abort because the fix has been applied here already. + ReseedFolderFlag.RerunGuardFlag = true; + + var legacyBaseItemWithUserKeys = new Dictionary<string, BaseItemEntity>(); + connection.Open(); + + var baseItemIds = new HashSet<Guid>(); + using (var operation = GetPreparedDbContext("Moving TypedBaseItem")) + { + IDictionary<Guid, (BaseItemEntity BaseItem, string[] Keys)> allItemsLookup = new Dictionary<Guid, (BaseItemEntity BaseItem, string[] Keys)>(); + const string typedBaseItemsQuery = + """ + SELECT guid, type, data, StartDate, EndDate, ChannelId, IsMovie, + IsSeries, EpisodeTitle, IsRepeat, CommunityRating, CustomRating, IndexNumber, IsLocked, PreferredMetadataLanguage, + PreferredMetadataCountryCode, Width, Height, DateLastRefreshed, Name, Path, PremiereDate, Overview, ParentIndexNumber, + ProductionYear, OfficialRating, ForcedSortName, RunTimeTicks, Size, DateCreated, DateModified, Genres, ParentId, TopParentId, + Audio, ExternalServiceId, IsInMixedFolder, DateLastSaved, LockedFields, Studios, Tags, TrailerTypes, OriginalTitle, PrimaryVersionId, + DateLastMediaAdded, Album, LUFS, NormalizationGain, CriticRating, IsVirtualItem, SeriesName, UserDataKey, SeasonName, SeasonId, SeriesId, + PresentationUniqueKey, InheritedParentalRatingValue, ExternalSeriesId, Tagline, ProviderIds, Images, ProductionLocations, ExtraIds, TotalBitrate, + ExtraType, Artists, AlbumArtists, ExternalId, SeriesPresentationUniqueKey, ShowId, OwnerId, MediaType, SortName, CleanName, UnratedType, IsFolder FROM TypedBaseItems + """; + using (new TrackedMigrationStep("Loading TypedBaseItems", _logger)) + { + foreach (SqliteDataReader dto in connection.Query(typedBaseItemsQuery)) + { + var baseItem = GetItem(dto); + allItemsLookup.Add(baseItem.BaseItem.Id, baseItem); + } + } + + bool DoesResolve(Guid? parentId, HashSet<(BaseItemEntity BaseItem, string[] Keys)> checkStack) + { + if (parentId is null) + { + return true; + } + + if (!allItemsLookup.TryGetValue(parentId.Value, out var parent)) + { + return false; // item is detached and has no root anymore. + } + + if (!checkStack.Add(parent)) + { + return false; // recursive structure. Abort. + } + + return DoesResolve(parent.BaseItem.ParentId, checkStack); + } + + using (new TrackedMigrationStep("Clean TypedBaseItems hierarchy", _logger)) + { + var checkStack = new HashSet<(BaseItemEntity BaseItem, string[] Keys)>(); + + foreach (var item in allItemsLookup) + { + var cachedItem = item.Value; + if (DoesResolve(cachedItem.BaseItem.ParentId, checkStack)) + { + checkStack.Add(cachedItem); + operation.JellyfinDbContext.BaseItems.Add(cachedItem.BaseItem); + baseItemIds.Add(cachedItem.BaseItem.Id); + foreach (var dataKey in cachedItem.Keys) + { + legacyBaseItemWithUserKeys[dataKey] = cachedItem.BaseItem; + } + } + + checkStack.Clear(); + } + } + + using (new TrackedMigrationStep($"Saving {operation.JellyfinDbContext.BaseItems.Local.Count} BaseItem entries", _logger)) + { + operation.JellyfinDbContext.SaveChanges(); + } + + allItemsLookup.Clear(); + } + + using (var operation = GetPreparedDbContext("Moving ItemValues")) + { + // do not migrate inherited types as they are now properly mapped in search and lookup. + const string itemValueQuery = + """ + SELECT ItemId, Type, Value, CleanValue FROM ItemValues + WHERE Type <> 6 AND EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = ItemValues.ItemId) + """; + + // EFCores local lookup sucks. We cannot use context.ItemValues.Local here because its just super slow. + var localItems = new Dictionary<(int Type, string Value), (Database.Implementations.Entities.ItemValue ItemValue, List<Guid> ItemIds)>(); + using (new TrackedMigrationStep("Loading ItemValues", _logger)) + { + foreach (SqliteDataReader dto in connection.Query(itemValueQuery)) + { + var itemId = dto.GetGuid(0); + if (!baseItemIds.Contains(itemId)) + { + continue; + } + + var entity = GetItemValue(dto); + var key = ((int)entity.Type, entity.Value); + if (!localItems.TryGetValue(key, out var existing)) + { + localItems[key] = existing = (entity, []); + } + + existing.ItemIds.Add(itemId); + } + + foreach (var item in localItems) + { + operation.JellyfinDbContext.ItemValues.Add(item.Value.ItemValue); + operation.JellyfinDbContext.ItemValuesMap.AddRange(item.Value.ItemIds.Distinct().Select(f => new ItemValueMap() + { + Item = null!, + ItemValue = null!, + ItemId = f, + ItemValueId = item.Value.ItemValue.ItemValueId + })); + } + } + + using (new TrackedMigrationStep($"Saving {operation.JellyfinDbContext.ItemValues.Local.Count} ItemValues entries", _logger)) + { + operation.JellyfinDbContext.SaveChanges(); + } + } + + using (var operation = GetPreparedDbContext("Moving UserData")) + { + var queryResult = connection.Query( + """ + SELECT key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex FROM UserDatas + + WHERE EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.UserDataKey = UserDatas.key) + """); + + using (new TrackedMigrationStep("Loading UserData", _logger)) + { + var users = operation.JellyfinDbContext.Users.AsNoTracking().ToArray(); + var userIdBlacklist = new HashSet<int>(); + + foreach (var entity in queryResult) + { + var userData = GetUserData(users, entity, userIdBlacklist, _logger); + if (userData is null) + { + var userDataId = entity.GetString(0); + var internalUserId = entity.GetInt32(1); + + if (!userIdBlacklist.Contains(internalUserId)) + { + _logger.LogError("Was not able to migrate user data with key {0} because its id {InternalId} does not match any existing user.", userDataId, internalUserId); + userIdBlacklist.Add(internalUserId); + } + + continue; + } + + if (!legacyBaseItemWithUserKeys.TryGetValue(userData.CustomDataKey!, out var refItem)) + { + _logger.LogError("Was not able to migrate user data with key {0} because it does not reference a valid BaseItem.", entity.GetString(0)); + continue; + } + + if (!baseItemIds.Contains(refItem.Id)) + { + continue; + } + + userData.ItemId = refItem.Id; + operation.JellyfinDbContext.UserData.Add(userData); + } + } + + legacyBaseItemWithUserKeys.Clear(); + + using (new TrackedMigrationStep($"Saving {operation.JellyfinDbContext.UserData.Local.Count} UserData entries", _logger)) + { + operation.JellyfinDbContext.SaveChanges(); + } + } + + using (var operation = GetPreparedDbContext("Moving MediaStreamInfos")) + { + const string mediaStreamQuery = + """ + SELECT ItemId, StreamIndex, StreamType, Codec, Language, ChannelLayout, Profile, AspectRatio, Path, + IsInterlaced, BitRate, Channels, SampleRate, IsDefault, IsForced, IsExternal, Height, Width, + AverageFrameRate, RealFrameRate, Level, PixelFormat, BitDepth, IsAnamorphic, RefFrames, CodecTag, + Comment, NalLengthSize, IsAvc, Title, TimeBase, CodecTimeBase, ColorPrimaries, ColorSpace, ColorTransfer, + DvVersionMajor, DvVersionMinor, DvProfile, DvLevel, RpuPresentFlag, ElPresentFlag, BlPresentFlag, DvBlSignalCompatibilityId, IsHearingImpaired + FROM MediaStreams + WHERE EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = MediaStreams.ItemId) + """; + + using (new TrackedMigrationStep("Loading MediaStreamInfos", _logger)) + { + foreach (SqliteDataReader dto in connection.Query(mediaStreamQuery)) + { + var entity = GetMediaStream(dto); + if (!baseItemIds.Contains(entity.ItemId)) + { + continue; + } + + operation.JellyfinDbContext.MediaStreamInfos.Add(entity); + } + } + + using (new TrackedMigrationStep($"Saving {operation.JellyfinDbContext.MediaStreamInfos.Local.Count} MediaStreamInfos entries", _logger)) + { + operation.JellyfinDbContext.SaveChanges(); + } + } + + using (var operation = GetPreparedDbContext("Moving AttachmentStreamInfos")) + { + const string mediaAttachmentQuery = + """ + SELECT ItemId, AttachmentIndex, Codec, CodecTag, Comment, filename, MIMEType + FROM mediaattachments + WHERE EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = mediaattachments.ItemId) + """; + + using (new TrackedMigrationStep("Loading AttachmentStreamInfos", _logger)) + { + foreach (SqliteDataReader dto in connection.Query(mediaAttachmentQuery)) + { + var entity = GetMediaAttachment(dto); + if (!baseItemIds.Contains(entity.ItemId)) + { + continue; + } + + operation.JellyfinDbContext.AttachmentStreamInfos.Add(entity); + } + } + + using (new TrackedMigrationStep($"Saving {operation.JellyfinDbContext.AttachmentStreamInfos.Local.Count} AttachmentStreamInfos entries", _logger)) + { + operation.JellyfinDbContext.SaveChanges(); + } + } + + using (var operation = GetPreparedDbContext("Moving People")) + { + const string personsQuery = + """ + SELECT ItemId, Name, Role, PersonType, SortOrder, ListOrder FROM People + WHERE EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = People.ItemId) + """; + + var peopleCache = new Dictionary<string, (People Person, List<PeopleBaseItemMap> Items)>(); + + using (new TrackedMigrationStep("Loading People", _logger)) + { + foreach (SqliteDataReader reader in connection.Query(personsQuery)) + { + var itemId = reader.GetGuid(0); + if (!baseItemIds.Contains(itemId)) + { + _logger.LogError("Not saving person {0} because it's not in use by any BaseItem", reader.GetString(1)); + continue; + } + + var entity = GetPerson(reader); + if (!peopleCache.TryGetValue(entity.Name + "|" + entity.PersonType, out var personCache)) + { + peopleCache[entity.Name + "|" + entity.PersonType] = personCache = (entity, []); + } + + if (reader.TryGetString(2, out var role)) + { + } + + int? sortOrder = reader.IsDBNull(4) ? null : reader.GetInt32(4); + int? listOrder = reader.IsDBNull(5) ? null : reader.GetInt32(5); + + personCache.Items.Add(new PeopleBaseItemMap() + { + Item = null!, + ItemId = itemId, + People = null!, + PeopleId = personCache.Person.Id, + ListOrder = listOrder, + SortOrder = sortOrder, + Role = role + }); + } + + foreach (var item in peopleCache) + { + operation.JellyfinDbContext.Peoples.Add(item.Value.Person); + operation.JellyfinDbContext.PeopleBaseItemMap.AddRange(item.Value.Items.DistinctBy(e => (e.ItemId, e.PeopleId))); + } + + peopleCache.Clear(); + } + + using (new TrackedMigrationStep($"Saving {operation.JellyfinDbContext.Peoples.Local.Count} People entries and {operation.JellyfinDbContext.PeopleBaseItemMap.Local.Count} maps", _logger)) + { + operation.JellyfinDbContext.SaveChanges(); + } + } + + using (var operation = GetPreparedDbContext("Moving Chapters")) + { + const string chapterQuery = + """ + SELECT ItemId,StartPositionTicks,Name,ImagePath,ImageDateModified,ChapterIndex from Chapters2 + WHERE EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = Chapters2.ItemId) + """; + + using (new TrackedMigrationStep("Loading Chapters", _logger)) + { + foreach (SqliteDataReader dto in connection.Query(chapterQuery)) + { + var chapter = GetChapter(dto); + if (!baseItemIds.Contains(chapter.ItemId)) + { + continue; + } + + operation.JellyfinDbContext.Chapters.Add(chapter); + } + } + + using (new TrackedMigrationStep($"Saving {operation.JellyfinDbContext.Chapters.Local.Count} Chapters entries", _logger)) + { + operation.JellyfinDbContext.SaveChanges(); + } + } + + using (var operation = GetPreparedDbContext("Moving AncestorIds")) + { + const string ancestorIdsQuery = + """ + SELECT ItemId, AncestorId, AncestorIdText FROM AncestorIds + WHERE + EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = AncestorIds.ItemId) + AND + EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = AncestorIds.AncestorId) + """; + + using (new TrackedMigrationStep("Loading AncestorIds", _logger)) + { + foreach (SqliteDataReader dto in connection.Query(ancestorIdsQuery)) + { + var ancestorId = GetAncestorId(dto); + if (!baseItemIds.Contains(ancestorId.ItemId) || !baseItemIds.Contains(ancestorId.ParentItemId)) + { + continue; + } + + operation.JellyfinDbContext.AncestorIds.Add(ancestorId); + } + } + + using (new TrackedMigrationStep($"Saving {operation.JellyfinDbContext.AncestorIds.Local.Count} AncestorId entries", _logger)) + { + operation.JellyfinDbContext.SaveChanges(); + } + } + + connection.Close(); + + _logger.LogInformation("Migration of the Library.db done."); + _logger.LogInformation("Migrating Library db took {0}.", fullOperationTimer.Elapsed); + + SqliteConnection.ClearAllPools(); + + _logger.LogInformation("Move {0} to {1}.", libraryDbPath, libraryDbPath + ".old"); + File.Move(libraryDbPath, libraryDbPath + ".old", true); + } + + private DatabaseMigrationStep GetPreparedDbContext(string operationName) + { + var dbContext = _provider.CreateDbContext(); + dbContext.ChangeTracker.AutoDetectChangesEnabled = false; + dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + return new DatabaseMigrationStep(dbContext, operationName, _logger); + } + + internal static UserData? GetUserData(User[] users, SqliteDataReader dto, HashSet<int> userIdBlacklist, ILogger logger) + { + var internalUserId = dto.GetInt32(1); + if (userIdBlacklist.Contains(internalUserId)) + { + return null; + } + + var user = users.FirstOrDefault(e => e.InternalId == internalUserId); + if (user is null) + { + userIdBlacklist.Add(internalUserId); + + logger.LogError("Tried to find user with index '{Idx}' but there are only '{MaxIdx}' users.", internalUserId, users.Length); + return null; + } + + var oldKey = dto.GetString(0); + + return new UserData() + { + ItemId = Guid.NewGuid(), + CustomDataKey = oldKey, + UserId = user.Id, + Rating = dto.IsDBNull(2) ? null : dto.GetDouble(2), + Played = dto.GetBoolean(3), + PlayCount = dto.GetInt32(4), + IsFavorite = dto.GetBoolean(5), + PlaybackPositionTicks = dto.GetInt64(6), + LastPlayedDate = dto.IsDBNull(7) ? null : dto.GetDateTime(7), + AudioStreamIndex = dto.IsDBNull(8) ? null : dto.GetInt32(8), + SubtitleStreamIndex = dto.IsDBNull(9) ? null : dto.GetInt32(9), + Likes = null, + User = null!, + Item = null! + }; + } + + private AncestorId GetAncestorId(SqliteDataReader reader) + { + return new AncestorId() + { + ItemId = reader.GetGuid(0), + ParentItemId = reader.GetGuid(1), + Item = null!, + ParentItem = null! + }; + } + + /// <summary> + /// Gets the chapter. + /// </summary> + /// <param name="reader">The reader.</param> + /// <returns>ChapterInfo.</returns> + private Chapter GetChapter(SqliteDataReader reader) + { + var chapter = new Chapter + { + StartPositionTicks = reader.GetInt64(1), + ChapterIndex = reader.GetInt32(5), + Item = null!, + ItemId = reader.GetGuid(0), + }; + + if (reader.TryGetString(2, out var chapterName)) + { + chapter.Name = chapterName; + } + + if (reader.TryGetString(3, out var imagePath)) + { + chapter.ImagePath = imagePath; + } + + if (reader.TryReadDateTime(4, out var imageDateModified)) + { + chapter.ImageDateModified = imageDateModified; + } + + return chapter; + } + + private ItemValue GetItemValue(SqliteDataReader reader) + { + return new ItemValue + { + ItemValueId = Guid.NewGuid(), + Type = (ItemValueType)reader.GetInt32(1), + Value = reader.GetString(2), + CleanValue = reader.GetString(3), + }; + } + + private People GetPerson(SqliteDataReader reader) + { + var item = new People + { + Id = Guid.NewGuid(), + Name = reader.GetString(1), + }; + + if (reader.TryGetString(3, out var type)) + { + item.PersonType = type; + } + + return item; + } + + /// <summary> + /// Gets the media stream. + /// </summary> + /// <param name="reader">The reader.</param> + /// <returns>MediaStream.</returns> + private MediaStreamInfo GetMediaStream(SqliteDataReader reader) + { + var item = new MediaStreamInfo + { + StreamIndex = reader.GetInt32(1), + StreamType = Enum.Parse<MediaStreamTypeEntity>(reader.GetString(2)), + Item = null!, + ItemId = reader.GetGuid(0), + AspectRatio = null!, + ChannelLayout = null!, + Codec = null!, + IsInterlaced = false, + Language = null!, + Path = null!, + Profile = null!, + }; + + if (reader.TryGetString(3, out var codec)) + { + item.Codec = codec; + } + + if (reader.TryGetString(4, out var language)) + { + item.Language = language; + } + + if (reader.TryGetString(5, out var channelLayout)) + { + item.ChannelLayout = channelLayout; + } + + if (reader.TryGetString(6, out var profile)) + { + item.Profile = profile; + } + + if (reader.TryGetString(7, out var aspectRatio)) + { + item.AspectRatio = aspectRatio; + } + + if (reader.TryGetString(8, out var path)) + { + item.Path = path; + } + + item.IsInterlaced = reader.GetBoolean(9); + + if (reader.TryGetInt32(10, out var bitrate)) + { + item.BitRate = bitrate; + } + + if (reader.TryGetInt32(11, out var channels)) + { + item.Channels = channels; + } + + if (reader.TryGetInt32(12, out var sampleRate)) + { + item.SampleRate = sampleRate; + } + + item.IsDefault = reader.GetBoolean(13); + item.IsForced = reader.GetBoolean(14); + item.IsExternal = reader.GetBoolean(15); + + if (reader.TryGetInt32(16, out var width)) + { + item.Width = width; + } + + if (reader.TryGetInt32(17, out var height)) + { + item.Height = height; + } + + if (reader.TryGetSingle(18, out var averageFrameRate)) + { + item.AverageFrameRate = averageFrameRate; + } + + if (reader.TryGetSingle(19, out var realFrameRate)) + { + item.RealFrameRate = realFrameRate; + } + + if (reader.TryGetSingle(20, out var level)) + { + item.Level = level; + } + + if (reader.TryGetString(21, out var pixelFormat)) + { + item.PixelFormat = pixelFormat; + } + + if (reader.TryGetInt32(22, out var bitDepth)) + { + item.BitDepth = bitDepth; + } + + if (reader.TryGetBoolean(23, out var isAnamorphic)) + { + item.IsAnamorphic = isAnamorphic; + } + + if (reader.TryGetInt32(24, out var refFrames)) + { + item.RefFrames = refFrames; + } + + if (reader.TryGetString(25, out var codecTag)) + { + item.CodecTag = codecTag; + } + + if (reader.TryGetString(26, out var comment)) + { + item.Comment = comment; + } + + if (reader.TryGetString(27, out var nalLengthSize)) + { + item.NalLengthSize = nalLengthSize; + } + + if (reader.TryGetBoolean(28, out var isAVC)) + { + item.IsAvc = isAVC; + } + + if (reader.TryGetString(29, out var title)) + { + item.Title = title; + } + + if (reader.TryGetString(30, out var timeBase)) + { + item.TimeBase = timeBase; + } + + if (reader.TryGetString(31, out var codecTimeBase)) + { + item.CodecTimeBase = codecTimeBase; + } + + if (reader.TryGetString(32, out var colorPrimaries)) + { + item.ColorPrimaries = colorPrimaries; + } + + if (reader.TryGetString(33, out var colorSpace)) + { + item.ColorSpace = colorSpace; + } + + if (reader.TryGetString(34, out var colorTransfer)) + { + item.ColorTransfer = colorTransfer; + } + + if (reader.TryGetInt32(35, out var dvVersionMajor)) + { + item.DvVersionMajor = dvVersionMajor; + } + + if (reader.TryGetInt32(36, out var dvVersionMinor)) + { + item.DvVersionMinor = dvVersionMinor; + } + + if (reader.TryGetInt32(37, out var dvProfile)) + { + item.DvProfile = dvProfile; + } + + if (reader.TryGetInt32(38, out var dvLevel)) + { + item.DvLevel = dvLevel; + } + + if (reader.TryGetInt32(39, out var rpuPresentFlag)) + { + item.RpuPresentFlag = rpuPresentFlag; + } + + if (reader.TryGetInt32(40, out var elPresentFlag)) + { + item.ElPresentFlag = elPresentFlag; + } + + if (reader.TryGetInt32(41, out var blPresentFlag)) + { + item.BlPresentFlag = blPresentFlag; + } + + if (reader.TryGetInt32(42, out var dvBlSignalCompatibilityId)) + { + item.DvBlSignalCompatibilityId = dvBlSignalCompatibilityId; + } + + item.IsHearingImpaired = reader.TryGetBoolean(43, out var result) && result; + + // if (reader.TryGetInt32(44, out var rotation)) + // { + // item.Rotation = rotation; + // } + + return item; + } + + /// <summary> + /// Gets the attachment. + /// </summary> + /// <param name="reader">The reader.</param> + /// <returns>MediaAttachment.</returns> + private AttachmentStreamInfo GetMediaAttachment(SqliteDataReader reader) + { + var item = new AttachmentStreamInfo + { + Index = reader.GetInt32(1), + Item = null!, + ItemId = reader.GetGuid(0), + }; + + if (reader.TryGetString(2, out var codec)) + { + item.Codec = codec; + } + + if (reader.TryGetString(3, out var codecTag)) + { + item.CodecTag = codecTag; + } + + if (reader.TryGetString(4, out var comment)) + { + item.Comment = comment; + } + + if (reader.TryGetString(5, out var fileName)) + { + item.Filename = fileName; + } + + if (reader.TryGetString(6, out var mimeType)) + { + item.MimeType = mimeType; + } + + return item; + } + + private (BaseItemEntity BaseItem, string[] LegacyUserDataKey) GetItem(SqliteDataReader reader) + { + var entity = new BaseItemEntity() + { + Id = reader.GetGuid(0), + Type = reader.GetString(1), + }; + + var index = 2; + + if (reader.TryGetString(index++, out var data)) + { + entity.Data = data; + } + + if (reader.TryReadDateTime(index++, out var startDate)) + { + entity.StartDate = startDate; + } + + if (reader.TryReadDateTime(index++, out var endDate)) + { + entity.EndDate = endDate; + } + + if (reader.TryGetGuid(index++, out var guid)) + { + entity.ChannelId = guid; + } + + if (reader.TryGetBoolean(index++, out var isMovie)) + { + entity.IsMovie = isMovie; + } + + if (reader.TryGetBoolean(index++, out var isSeries)) + { + entity.IsSeries = isSeries; + } + + if (reader.TryGetString(index++, out var episodeTitle)) + { + entity.EpisodeTitle = episodeTitle; + } + + if (reader.TryGetBoolean(index++, out var isRepeat)) + { + entity.IsRepeat = isRepeat; + } + + if (reader.TryGetSingle(index++, out var communityRating)) + { + entity.CommunityRating = communityRating; + } + + if (reader.TryGetString(index++, out var customRating)) + { + entity.CustomRating = customRating; + } + + if (reader.TryGetInt32(index++, out var indexNumber)) + { + entity.IndexNumber = indexNumber; + } + + if (reader.TryGetBoolean(index++, out var isLocked)) + { + entity.IsLocked = isLocked; + } + + if (reader.TryGetString(index++, out var preferredMetadataLanguage)) + { + entity.PreferredMetadataLanguage = preferredMetadataLanguage; + } + + if (reader.TryGetString(index++, out var preferredMetadataCountryCode)) + { + entity.PreferredMetadataCountryCode = preferredMetadataCountryCode; + } + + if (reader.TryGetInt32(index++, out var width)) + { + entity.Width = width; + } + + if (reader.TryGetInt32(index++, out var height)) + { + entity.Height = height; + } + + if (reader.TryReadDateTime(index++, out var dateLastRefreshed)) + { + entity.DateLastRefreshed = dateLastRefreshed; + } + + if (reader.TryGetString(index++, out var name)) + { + entity.Name = name; + } + + if (reader.TryGetString(index++, out var restorePath)) + { + entity.Path = restorePath; + } + + if (reader.TryReadDateTime(index++, out var premiereDate)) + { + entity.PremiereDate = premiereDate; + } + + if (reader.TryGetString(index++, out var overview)) + { + entity.Overview = overview; + } + + if (reader.TryGetInt32(index++, out var parentIndexNumber)) + { + entity.ParentIndexNumber = parentIndexNumber; + } + + if (reader.TryGetInt32(index++, out var productionYear)) + { + entity.ProductionYear = productionYear; + } + + if (reader.TryGetString(index++, out var officialRating)) + { + entity.OfficialRating = officialRating; + } + + if (reader.TryGetString(index++, out var forcedSortName)) + { + entity.ForcedSortName = forcedSortName; + } + + if (reader.TryGetInt64(index++, out var runTimeTicks)) + { + entity.RunTimeTicks = runTimeTicks; + } + + if (reader.TryGetInt64(index++, out var size)) + { + entity.Size = size; + } + + if (reader.TryReadDateTime(index++, out var dateCreated)) + { + entity.DateCreated = dateCreated; + } + + if (reader.TryReadDateTime(index++, out var dateModified)) + { + entity.DateModified = dateModified; + } + + if (reader.TryGetString(index++, out var genres)) + { + entity.Genres = genres; + } + + if (reader.TryGetGuid(index++, out var parentId)) + { + entity.ParentId = parentId; + } + + if (reader.TryGetGuid(index++, out var topParentId)) + { + entity.TopParentId = topParentId; + } + + if (reader.TryGetString(index++, out var audioString) && Enum.TryParse<ProgramAudioEntity>(audioString, out var audioType)) + { + entity.Audio = audioType; + } + + if (reader.TryGetString(index++, out var serviceName)) + { + entity.ExternalServiceId = serviceName; + } + + if (reader.TryGetBoolean(index++, out var isInMixedFolder)) + { + entity.IsInMixedFolder = isInMixedFolder; + } + + if (reader.TryReadDateTime(index++, out var dateLastSaved)) + { + entity.DateLastSaved = dateLastSaved; + } + + if (reader.TryGetString(index++, out var lockedFields)) + { + entity.LockedFields = lockedFields.Split('|').Select(Enum.Parse<MetadataField>) + .Select(e => new BaseItemMetadataField() + { + Id = (int)e, + Item = entity, + ItemId = entity.Id + }) + .ToArray(); + } + + if (reader.TryGetString(index++, out var studios)) + { + entity.Studios = studios; + } + + if (reader.TryGetString(index++, out var tags)) + { + entity.Tags = tags; + } + + if (reader.TryGetString(index++, out var trailerTypes)) + { + entity.TrailerTypes = trailerTypes.Split('|').Select(Enum.Parse<TrailerType>) + .Select(e => new BaseItemTrailerType() + { + Id = (int)e, + Item = entity, + ItemId = entity.Id + }) + .ToArray(); + } + + if (reader.TryGetString(index++, out var originalTitle)) + { + entity.OriginalTitle = originalTitle; + } + + if (reader.TryGetString(index++, out var primaryVersionId)) + { + entity.PrimaryVersionId = primaryVersionId; + } + + if (reader.TryReadDateTime(index++, out var dateLastMediaAdded)) + { + entity.DateLastMediaAdded = dateLastMediaAdded; + } + + if (reader.TryGetString(index++, out var album)) + { + entity.Album = album; + } + + if (reader.TryGetSingle(index++, out var lUFS)) + { + entity.LUFS = lUFS; + } + + if (reader.TryGetSingle(index++, out var normalizationGain)) + { + entity.NormalizationGain = normalizationGain; + } + + if (reader.TryGetSingle(index++, out var criticRating)) + { + entity.CriticRating = criticRating; + } + + if (reader.TryGetBoolean(index++, out var isVirtualItem)) + { + entity.IsVirtualItem = isVirtualItem; + } + + if (reader.TryGetString(index++, out var seriesName)) + { + entity.SeriesName = seriesName; + } + + var userDataKeys = new List<string>(); + if (reader.TryGetString(index++, out var directUserDataKey)) + { + userDataKeys.Add(directUserDataKey); + } + + if (reader.TryGetString(index++, out var seasonName)) + { + entity.SeasonName = seasonName; + } + + if (reader.TryGetGuid(index++, out var seasonId)) + { + entity.SeasonId = seasonId; + } + + if (reader.TryGetGuid(index++, out var seriesId)) + { + entity.SeriesId = seriesId; + } + + if (reader.TryGetString(index++, out var presentationUniqueKey)) + { + entity.PresentationUniqueKey = presentationUniqueKey; + } + + if (reader.TryGetInt32(index++, out var parentalRating)) + { + entity.InheritedParentalRatingValue = parentalRating; + } + + if (reader.TryGetString(index++, out var externalSeriesId)) + { + entity.ExternalSeriesId = externalSeriesId; + } + + if (reader.TryGetString(index++, out var tagLine)) + { + entity.Tagline = tagLine; + } + + if (reader.TryGetString(index++, out var providerIds)) + { + entity.Provider = providerIds.Split('|').Select(e => e.Split("=")).Where(e => e.Length >= 2) + .Select(e => new BaseItemProvider() + { + Item = null!, + ProviderId = e[0], + ProviderValue = string.Join('|', e.Skip(1)) + }) + .DistinctBy(e => e.ProviderId) + .ToArray(); + } + + if (reader.TryGetString(index++, out var imageInfos)) + { + entity.Images = DeserializeImages(imageInfos).Select(f => Map(entity.Id, f)).ToArray(); + } + + if (reader.TryGetString(index++, out var productionLocations)) + { + entity.ProductionLocations = productionLocations; + } + + if (reader.TryGetString(index++, out var extraIds)) + { + entity.ExtraIds = extraIds; + } + + if (reader.TryGetInt32(index++, out var totalBitrate)) + { + entity.TotalBitrate = totalBitrate; + } + + if (reader.TryGetString(index++, out var extraTypeString) && Enum.TryParse<BaseItemExtraType>(extraTypeString, out var extraType)) + { + entity.ExtraType = extraType; + } + + if (reader.TryGetString(index++, out var artists)) + { + entity.Artists = artists; + } + + if (reader.TryGetString(index++, out var albumArtists)) + { + entity.AlbumArtists = albumArtists; + } + + if (reader.TryGetString(index++, out var externalId)) + { + entity.ExternalId = externalId; + } + + if (reader.TryGetString(index++, out var seriesPresentationUniqueKey)) + { + entity.SeriesPresentationUniqueKey = seriesPresentationUniqueKey; + } + + if (reader.TryGetString(index++, out var showId)) + { + entity.ShowId = showId; + } + + if (reader.TryGetString(index++, out var ownerId)) + { + entity.OwnerId = ownerId; + } + + if (reader.TryGetString(index++, out var mediaType)) + { + entity.MediaType = mediaType; + } + + if (reader.TryGetString(index++, out var sortName)) + { + entity.SortName = sortName; + } + + if (reader.TryGetString(index++, out var cleanName)) + { + entity.CleanName = cleanName; + } + + if (reader.TryGetString(index++, out var unratedType)) + { + entity.UnratedType = unratedType; + } + + if (reader.TryGetBoolean(index++, out var isFolder)) + { + entity.IsFolder = isFolder; + } + + var baseItem = BaseItemRepository.DeserializeBaseItem(entity, _logger, null, false); + if (baseItem is not null) + { + var dataKeys = baseItem.GetUserDataKeys(); + userDataKeys.AddRange(dataKeys); + } + + return (entity, userDataKeys.ToArray()); + } + + private static BaseItemImageInfo Map(Guid baseItemId, ItemImageInfo e) + { + return new BaseItemImageInfo() + { + ItemId = baseItemId, + Id = Guid.NewGuid(), + Path = e.Path, + Blurhash = e.BlurHash is not null ? Encoding.UTF8.GetBytes(e.BlurHash) : null, + DateModified = e.DateModified, + Height = e.Height, + Width = e.Width, + ImageType = (ImageInfoImageType)e.Type, + Item = null! + }; + } + + internal ItemImageInfo[] DeserializeImages(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return Array.Empty<ItemImageInfo>(); + } + + // TODO The following is an ugly performance optimization, but it's extremely unlikely that the data in the database would be malformed + var valueSpan = value.AsSpan(); + var count = valueSpan.Count('|') + 1; + + var position = 0; + var result = new ItemImageInfo[count]; + foreach (var part in valueSpan.Split('|')) + { + var image = ItemImageInfoFromValueString(part); + + if (image is not null) + { + result[position++] = image; + } + } + + if (position == count) + { + return result; + } + + if (position == 0) + { + return Array.Empty<ItemImageInfo>(); + } + + // Extremely unlikely, but somehow one or more of the image strings were malformed. Cut the array. + return result[..position]; + } + + internal ItemImageInfo? ItemImageInfoFromValueString(ReadOnlySpan<char> value) + { + const char Delimiter = '*'; + + var nextSegment = value.IndexOf(Delimiter); + if (nextSegment == -1) + { + return null; + } + + ReadOnlySpan<char> path = value[..nextSegment]; + value = value[(nextSegment + 1)..]; + nextSegment = value.IndexOf(Delimiter); + if (nextSegment == -1) + { + return null; + } + + ReadOnlySpan<char> dateModified = value[..nextSegment]; + value = value[(nextSegment + 1)..]; + nextSegment = value.IndexOf(Delimiter); + if (nextSegment == -1) + { + nextSegment = value.Length; + } + + ReadOnlySpan<char> imageType = value[..nextSegment]; + + var image = new ItemImageInfo + { + Path = path.ToString() + }; + + if (long.TryParse(dateModified, CultureInfo.InvariantCulture, out var ticks) + && ticks >= DateTime.MinValue.Ticks + && ticks <= DateTime.MaxValue.Ticks) + { + image.DateModified = new DateTime(ticks, DateTimeKind.Utc); + } + else + { + return null; + } + + if (Enum.TryParse(imageType, true, out ImageType type)) + { + image.Type = type; + } + else + { + return null; + } + + // Optional parameters: width*height*blurhash + if (nextSegment + 1 < value.Length - 1) + { + value = value[(nextSegment + 1)..]; + nextSegment = value.IndexOf(Delimiter); + if (nextSegment == -1 || nextSegment == value.Length) + { + return image; + } + + ReadOnlySpan<char> widthSpan = value[..nextSegment]; + + value = value[(nextSegment + 1)..]; + nextSegment = value.IndexOf(Delimiter); + if (nextSegment == -1) + { + nextSegment = value.Length; + } + + ReadOnlySpan<char> heightSpan = value[..nextSegment]; + + if (int.TryParse(widthSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var width) + && int.TryParse(heightSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var height)) + { + image.Width = width; + image.Height = height; + } + + if (nextSegment < value.Length - 1) + { + value = value[(nextSegment + 1)..]; + var length = value.Length; + + Span<char> blurHashSpan = stackalloc char[length]; + for (int i = 0; i < length; i++) + { + var c = value[i]; + blurHashSpan[i] = c switch + { + '/' => Delimiter, + '\\' => '|', + _ => c + }; + } + + image.BlurHash = new string(blurHashSpan); + } + } + + return image; + } + + private class TrackedMigrationStep : IDisposable + { + private readonly string _operationName; + private readonly ILogger _logger; + private readonly Stopwatch _operationTimer; + private bool _disposed; + + public TrackedMigrationStep(string operationName, ILogger logger) + { + _operationName = operationName; + _logger = logger; + _operationTimer = Stopwatch.StartNew(); + logger.LogInformation("Start {OperationName}", operationName); + } + + public bool Disposed + { + get => _disposed; + set => _disposed = value; + } + + public virtual void Dispose() + { + if (Disposed) + { + return; + } + + Disposed = true; + _logger.LogInformation("{OperationName} took '{Time}'", _operationName, _operationTimer.Elapsed); + } + } + + private sealed class DatabaseMigrationStep : TrackedMigrationStep + { + public DatabaseMigrationStep(JellyfinDbContext jellyfinDbContext, string operationName, ILogger logger) : base(operationName, logger) + { + JellyfinDbContext = jellyfinDbContext; + } + + public JellyfinDbContext JellyfinDbContext { get; } + + public override void Dispose() + { + if (Disposed) + { + return; + } + + JellyfinDbContext.Dispose(); + base.Dispose(); + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/MigrateLibraryDbCompatibilityCheck.cs b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDbCompatibilityCheck.cs new file mode 100644 index 00000000..d4cc9bbe --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDbCompatibilityCheck.cs @@ -0,0 +1,73 @@ +#pragma warning disable RS0030 // Do not use banned APIs + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Server.ServerSetupApp; +using MediaBrowser.Controller; +using Microsoft.Data.Sqlite; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// The migration routine for checking if the current instance of Jellyfin is compatiable to be upgraded. +/// </summary> +[JellyfinMigration("2025-04-20T19:30:00", nameof(MigrateLibraryDbCompatibilityCheck))] +public class MigrateLibraryDbCompatibilityCheck : IAsyncMigrationRoutine +{ + private const string DbFilename = "library.db"; + private readonly IStartupLogger _logger; + private readonly IServerApplicationPaths _paths; + + /// <summary> + /// Initializes a new instance of the <see cref="MigrateLibraryDbCompatibilityCheck"/> class. + /// </summary> + /// <param name="startupLogger">The startup logger.</param> + /// <param name="paths">The Path service.</param> + public MigrateLibraryDbCompatibilityCheck(IStartupLogger<MigrateLibraryDbCompatibilityCheck> startupLogger, IServerApplicationPaths paths) + { + _logger = startupLogger; + _paths = paths; + } + + /// <inheritdoc/> + public async Task PerformAsync(CancellationToken cancellationToken) + { + var dataPath = _paths.DataPath; + var libraryDbPath = Path.Combine(dataPath, DbFilename); + if (!File.Exists(libraryDbPath)) + { + _logger.LogError("Cannot migrate {LibraryDb} as it does not exist..", libraryDbPath); + return; + } + + using var connection = new SqliteConnection($"Filename={libraryDbPath};Mode=ReadOnly"); + await connection.OpenAsync(cancellationToken).ConfigureAwait(false); + CheckMigratableVersion(connection); + await connection.CloseAsync().ConfigureAwait(false); + } + + private static void CheckMigratableVersion(SqliteConnection connection) + { + CheckColumnExistance(connection, "TypedBaseItems", "lufs"); + CheckColumnExistance(connection, "TypedBaseItems", "normalizationgain"); + CheckColumnExistance(connection, "mediastreams", "dvversionmajor"); + + static void CheckColumnExistance(SqliteConnection connection, string table, string column) + { + using (var cmd = connection.CreateCommand()) + { +#pragma warning disable CA2100 // Review SQL queries for security vulnerabilities + cmd.CommandText = $"Select COUNT(1) FROM pragma_table_xinfo('{table}') WHERE lower(name) = '{column}';"; +#pragma warning restore CA2100 // Review SQL queries for security vulnerabilities + var result = cmd.ExecuteScalar()!; + if (!result.Equals(1L)) + { + throw new InvalidOperationException("Your database does not meet the required standard. Only upgrades from server version 10.9.11 or above are supported. Please upgrade first to server version 10.10.7 before attempting to upgrade afterwards to 10.11"); + } + } + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/MigrateLibraryUserData.cs b/Jellyfin.Server/Migrations/Routines/MigrateLibraryUserData.cs new file mode 100644 index 00000000..8a0a1741 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MigrateLibraryUserData.cs @@ -0,0 +1,123 @@ +#pragma warning disable RS0030 // Do not use banned APIs + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Implementations.Data; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Server.Implementations.Item; +using Jellyfin.Server.ServerSetupApp; +using MediaBrowser.Controller; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +[JellyfinMigration("2025-06-18T01:00:00", nameof(MigrateLibraryUserData))] +[JellyfinMigrationBackup(JellyfinDb = true)] +internal class MigrateLibraryUserData : IAsyncMigrationRoutine +{ + private const string DbFilename = "library.db.old"; + + private readonly IStartupLogger _logger; + private readonly IServerApplicationPaths _paths; + private readonly IDbContextFactory<JellyfinDbContext> _provider; + + public MigrateLibraryUserData( + IStartupLogger<MigrateLibraryDb> startupLogger, + IDbContextFactory<JellyfinDbContext> provider, + IServerApplicationPaths paths) + { + _logger = startupLogger; + _provider = provider; + _paths = paths; + } + + public async Task PerformAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Migrating the userdata from library.db.old may take a while, do not stop Jellyfin."); + + var dataPath = _paths.DataPath; + var libraryDbPath = Path.Combine(dataPath, DbFilename); + if (!File.Exists(libraryDbPath)) + { + _logger.LogError("Cannot migrate userdata from {LibraryDb} as it does not exist. This migration expects the MigrateLibraryDb to run first.", libraryDbPath); + return; + } + + var dbContext = await _provider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + if (!await dbContext.BaseItems.AnyAsync(e => e.Id == BaseItemRepository.PlaceholderId, cancellationToken).ConfigureAwait(false)) + { + // the placeholder baseitem has been deleted by the librarydb migration so we need to readd it. + await dbContext.BaseItems.AddAsync( + new Database.Implementations.Entities.BaseItemEntity() + { + Id = BaseItemRepository.PlaceholderId, + Type = "PLACEHOLDER", + Name = "This is a placeholder item for UserData that has been detacted from its original item" + }, + cancellationToken) + .ConfigureAwait(false); + await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + } + + var users = dbContext.Users.AsNoTracking().ToArray(); + var userIdBlacklist = new HashSet<int>(); + using var connection = new SqliteConnection($"Filename={libraryDbPath};Mode=ReadOnly"); + var retentionDate = DateTime.UtcNow; + + var queryResult = connection.Query( +""" + SELECT key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex FROM UserDatas + + WHERE NOT EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.UserDataKey = UserDatas.key) +"""); + + var importedUserData = new Dictionary<Guid, List<UserData>>(); + foreach (var entity in queryResult) + { + var userData = MigrateLibraryDb.GetUserData(users, entity, userIdBlacklist, _logger); + if (userData is null) + { + var userDataId = entity.GetString(0); + var internalUserId = entity.GetInt32(1); + + if (!userIdBlacklist.Contains(internalUserId)) + { + _logger.LogError("Was not able to migrate user data with key {0} because its id {InternalId} does not match any existing user.", userDataId, internalUserId); + userIdBlacklist.Add(internalUserId); + } + + continue; + } + + var ogId = userData.ItemId; + userData.ItemId = BaseItemRepository.PlaceholderId; + userData.RetentionDate = retentionDate; + if (!importedUserData.TryGetValue(ogId, out var importUserData)) + { + importUserData = []; + importedUserData[ogId] = importUserData; + } + + importUserData.Add(userData); + } + + foreach (var item in importedUserData) + { + await dbContext.UserData.Where(e => e.ItemId == item.Key).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false); + dbContext.UserData.AddRange(item.Value.DistinctBy(e => e.CustomDataKey)); // old userdata can have fucked up duplicates + } + + _logger.LogInformation("Try saving {NewSaved} UserData entries.", dbContext.UserData.Local.Count); + await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs b/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs new file mode 100644 index 00000000..2a6db01c --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs @@ -0,0 +1,69 @@ +using System; +using System.Linq; +using Jellyfin.Database.Implementations; +using Jellyfin.Server.ServerSetupApp; +using MediaBrowser.Model.Globalization; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Migrate rating levels. +/// </summary> +#pragma warning disable CS0618 // Type or member is obsolete +[JellyfinMigration("2025-04-20T22:00:00", nameof(MigrateRatingLevels))] +[JellyfinMigrationBackup(JellyfinDb = true)] +#pragma warning restore CS0618 // Type or member is obsolete +internal class MigrateRatingLevels : IDatabaseMigrationRoutine +{ + private readonly IStartupLogger _logger; + private readonly IDbContextFactory<JellyfinDbContext> _provider; + private readonly ILocalizationManager _localizationManager; + + public MigrateRatingLevels( + IDbContextFactory<JellyfinDbContext> provider, + IStartupLogger<MigrateRatingLevels> logger, + ILocalizationManager localizationManager) + { + _provider = provider; + _localizationManager = localizationManager; + _logger = logger; + } + + /// <inheritdoc/> + public void Perform() + { + _logger.LogInformation("Recalculating parental rating levels based on rating string."); + using var context = _provider.CreateDbContext(); + using var transaction = context.Database.BeginTransaction(); + var ratings = context.BaseItems.AsNoTracking().Select(e => e.OfficialRating).Distinct(); + foreach (var rating in ratings) + { + if (string.IsNullOrEmpty(rating)) + { + int? value = null; + context.BaseItems + .Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty) + .ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingValue, value)); + context.BaseItems + .Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty) + .ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingSubValue, value)); + } + else + { + var ratingValue = _localizationManager.GetRatingScore(rating); + var score = ratingValue?.Score; + var subScore = ratingValue?.SubScore; + context.BaseItems + .Where(e => e.OfficialRating == rating) + .ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingValue, score)); + context.BaseItems + .Where(e => e.OfficialRating == rating) + .ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingSubValue, subScore)); + } + } + + transaction.Commit(); + } +} diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs new file mode 100644 index 00000000..8c3361ee --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -0,0 +1,233 @@ +using System; +using System.IO; +using Emby.Server.Implementations.Data; +using Jellyfin.Data; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using Jellyfin.Extensions.Json; +using Jellyfin.Server.Implementations.Users; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Users; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// The migration routine for migrating the user database to EF Core. +/// </summary> +#pragma warning disable CS0618 // Type or member is obsolete +[JellyfinMigration("2025-04-20T10:00:00", nameof(MigrateUserDb), "5C4B82A2-F053-4009-BD05-B6FCAD82F14C")] +public class MigrateUserDb : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete +{ + private const string DbFilename = "users.db"; + + private readonly ILogger<MigrateUserDb> _logger; + private readonly IServerApplicationPaths _paths; + private readonly IDbContextFactory<JellyfinDbContext> _provider; + private readonly IXmlSerializer _xmlSerializer; + + /// <summary> + /// Initializes a new instance of the <see cref="MigrateUserDb"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="paths">The server application paths.</param> + /// <param name="provider">The database provider.</param> + /// <param name="xmlSerializer">The xml serializer.</param> + public MigrateUserDb( + ILogger<MigrateUserDb> logger, + IServerApplicationPaths paths, + IDbContextFactory<JellyfinDbContext> provider, + IXmlSerializer xmlSerializer) + { + _logger = logger; + _paths = paths; + _provider = provider; + _xmlSerializer = xmlSerializer; + } + + /// <inheritdoc/> + public void Perform() + { + var dataPath = _paths.DataPath; + var userDbPath = Path.Combine(dataPath, DbFilename); + if (!File.Exists(userDbPath)) + { + _logger.LogWarning("{UserDbPath} doesn't exist, nothing to migrate", userDbPath); + return; + } + + _logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin."); + + using (var connection = new SqliteConnection($"Filename={userDbPath}")) + { + connection.Open(); + var tableQuery = connection.Query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='LocalUsersv2';"); + foreach (var row in tableQuery) + { + if (row.GetInt32(0) == 0) + { + _logger.LogWarning("Table 'LocalUsersv2' doesn't exist in {UserDbPath}, nothing to migrate", userDbPath); + return; + } + } + + using var dbContext = _provider.CreateDbContext(); + + var queryResult = connection.Query("SELECT * FROM LocalUsersv2"); + + dbContext.RemoveRange(dbContext.Users); + dbContext.SaveChanges(); + + foreach (var entry in queryResult) + { + UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry.GetStream(2), JsonDefaults.Options); + if (mockup is null) + { + continue; + } + + var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name); + + var configPath = Path.Combine(userDataDir, "config.xml"); + var config = File.Exists(configPath) + ? (UserConfiguration?)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), configPath) ?? new UserConfiguration() + : new UserConfiguration(); + + var policyPath = Path.Combine(userDataDir, "policy.xml"); + var policy = File.Exists(policyPath) + ? (UserPolicy?)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), policyPath) ?? new UserPolicy() + : new UserPolicy(); + policy.AuthenticationProviderId = policy.AuthenticationProviderId?.Replace( + "Emby.Server.Implementations.Library", + "Jellyfin.Server.Implementations.Users", + StringComparison.Ordinal) + ?? typeof(DefaultAuthenticationProvider).FullName; + + policy.PasswordResetProviderId = typeof(DefaultPasswordResetProvider).FullName; + int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch + { + -1 => null, + 0 => 3, + _ => policy.LoginAttemptsBeforeLockout + }; + + var user = new User(mockup.Name, policy.AuthenticationProviderId!, policy.PasswordResetProviderId!) + { + Id = entry.GetGuid(1), + InternalId = entry.GetInt64(0), + MaxParentalRatingScore = policy.MaxParentalRating, + MaxParentalRatingSubScore = null, + EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess, + RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit, + InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount, + LoginAttemptsBeforeLockout = maxLoginAttempts, + SubtitleMode = config.SubtitleMode, + HidePlayedInLatest = config.HidePlayedInLatest, + EnableLocalPassword = config.EnableLocalPassword, + PlayDefaultAudioTrack = config.PlayDefaultAudioTrack, + DisplayCollectionsView = config.DisplayCollectionsView, + DisplayMissingEpisodes = config.DisplayMissingEpisodes, + AudioLanguagePreference = config.AudioLanguagePreference, + RememberAudioSelections = config.RememberAudioSelections, + EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay, + RememberSubtitleSelections = config.RememberSubtitleSelections, + SubtitleLanguagePreference = config.SubtitleLanguagePreference, + Password = mockup.Password, + LastLoginDate = mockup.LastLoginDate, + LastActivityDate = mockup.LastActivityDate + }; + + if (mockup.ImageInfos.Length > 0) + { + ItemImageInfo info = mockup.ImageInfos[0]; + + user.ProfileImage = new ImageInfo(info.Path) + { + LastModified = info.DateModified + }; + } + + user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator); + user.SetPermission(PermissionKind.IsHidden, policy.IsHidden); + user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled); + user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl); + user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess); + user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement); + user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess); + user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback); + user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding); + user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding); + user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion); + user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading); + user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding); + user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion); + user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels); + user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices); + user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders); + user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers); + user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing); + user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding); + user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing); + user.SetPermission(PermissionKind.EnableCollectionManagement, policy.EnableCollectionManagement); + + foreach (var policyAccessSchedule in policy.AccessSchedules) + { + user.AccessSchedules.Add(policyAccessSchedule); + } + + user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags); + user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels); + user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices); + user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders); + user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders); + user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews); + user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders); + user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes); + user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes); + + dbContext.Users.Add(user); + } + + dbContext.SaveChanges(); + } + + try + { + File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old")); + + var journalPath = Path.Combine(dataPath, DbFilename + "-journal"); + if (File.Exists(journalPath)) + { + File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal")); + } + } + catch (IOException e) + { + _logger.LogError(e, "Error renaming legacy user database to 'users.db.old'"); + } + } + +#nullable disable + internal class UserMockup + { + public string Password { get; set; } + + public string EasyPassword { get; set; } + + public DateTime? LastLoginDate { get; set; } + + public DateTime? LastActivityDate { get; set; } + + public string Name { get; set; } + + public ItemImageInfo[] ImageInfos { get; set; } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs b/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs new file mode 100644 index 00000000..fbf9c163 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs @@ -0,0 +1,319 @@ +#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Server.ServerSetupApp; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.IO; +using MediaBrowser.Model.IO; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Migration to move extracted files to the new directories. +/// </summary> +[JellyfinMigration("2025-04-20T21:00:00", nameof(MoveExtractedFiles))] +public class MoveExtractedFiles : IAsyncMigrationRoutine +{ + private readonly IApplicationPaths _appPaths; + private readonly ILogger _logger; + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + private readonly IPathManager _pathManager; + private readonly IFileSystem _fileSystem; + + /// <summary> + /// Initializes a new instance of the <see cref="MoveExtractedFiles"/> class. + /// </summary> + /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param> + /// <param name="logger">The logger.</param> + /// <param name="startupLogger">The startup logger for Startup UI intigration.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="dbProvider">Instance of the <see cref="IDbContextFactory{JellyfinDbContext}"/> interface.</param> + public MoveExtractedFiles( + IApplicationPaths appPaths, + ILogger<MoveExtractedFiles> logger, + IStartupLogger<MoveExtractedFiles> startupLogger, + IPathManager pathManager, + IFileSystem fileSystem, + IDbContextFactory<JellyfinDbContext> dbProvider) + { + _appPaths = appPaths; + _logger = startupLogger.With(logger); + _pathManager = pathManager; + _fileSystem = fileSystem; + _dbProvider = dbProvider; + } + + private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles"); + + private string AttachmentCachePath => Path.Combine(_appPaths.DataPath, "attachments"); + + /// <inheritdoc /> + public async Task PerformAsync(CancellationToken cancellationToken) + { + const int Limit = 5000; + int itemCount = 0; + + var sw = Stopwatch.StartNew(); + + using var context = _dbProvider.CreateDbContext(); + var records = context.BaseItems.Count(b => b.MediaType == MediaType.Video.ToString() && !b.IsVirtualItem && !b.IsFolder); + _logger.LogInformation("Checking {Count} items for movable extracted files.", records); + + // Make sure directories exist + Directory.CreateDirectory(SubtitleCachePath); + Directory.CreateDirectory(AttachmentCachePath); + + await foreach (var result in context.BaseItems + .Include(e => e.MediaStreams!.Where(s => s.StreamType == MediaStreamTypeEntity.Subtitle && !s.IsExternal)) + .Where(b => b.MediaType == MediaType.Video.ToString() && !b.IsVirtualItem && !b.IsFolder) + .Select(b => new + { + b.Id, + b.Path, + b.MediaStreams + }) + .OrderBy(e => e.Id) + .WithPartitionProgress((partition) => _logger.LogInformation("Checked: {Count} - Moved: {Items} - Time: {Time}", partition * Limit, itemCount, sw.Elapsed)) + .PartitionEagerAsync(Limit, cancellationToken) + .WithCancellation(cancellationToken) + .ConfigureAwait(false)) + { + if (MoveSubtitleAndAttachmentFiles(result.Id, result.Path, result.MediaStreams, context)) + { + itemCount++; + } + } + + _logger.LogInformation("Moved files for {Count} items in {Time}", itemCount, sw.Elapsed); + + // Get all subdirectories with 1 character names (those are the legacy directories) + var subdirectories = Directory.GetDirectories(SubtitleCachePath, "*", SearchOption.AllDirectories).Where(s => s.Length == SubtitleCachePath.Length + 2).ToList(); + subdirectories.AddRange(Directory.GetDirectories(AttachmentCachePath, "*", SearchOption.AllDirectories).Where(s => s.Length == AttachmentCachePath.Length + 2)); + + // Remove all legacy subdirectories + foreach (var subdir in subdirectories) + { + Directory.Delete(subdir, true); + } + + // Remove old cache path + var attachmentCachePath = Path.Join(_appPaths.CachePath, "attachments"); + if (Directory.Exists(attachmentCachePath)) + { + Directory.Delete(attachmentCachePath, true); + } + + _logger.LogInformation("Cleaned up left over subtitles and attachments."); + } + + private bool MoveSubtitleAndAttachmentFiles(Guid id, string? path, ICollection<MediaStreamInfo>? mediaStreams, JellyfinDbContext context) + { + var itemIdString = id.ToString("N", CultureInfo.InvariantCulture); + var modified = false; + if (mediaStreams is not null) + { + foreach (var mediaStream in mediaStreams) + { + if (mediaStream.Codec is null) + { + continue; + } + + var mediaStreamIndex = mediaStream.StreamIndex; + var extension = GetSubtitleExtension(mediaStream.Codec); + var oldSubtitleCachePath = GetOldSubtitleCachePath(path, mediaStreamIndex, extension); + if (string.IsNullOrEmpty(oldSubtitleCachePath) || !File.Exists(oldSubtitleCachePath)) + { + continue; + } + + var newSubtitleCachePath = _pathManager.GetSubtitlePath(itemIdString, mediaStreamIndex, extension); + if (File.Exists(newSubtitleCachePath)) + { + File.Delete(oldSubtitleCachePath); + } + else + { + var newDirectory = Path.GetDirectoryName(newSubtitleCachePath); + if (newDirectory is not null) + { + Directory.CreateDirectory(newDirectory); + File.Move(oldSubtitleCachePath, newSubtitleCachePath, false); + _logger.LogDebug("Moved subtitle {Index} for {Item} from {Source} to {Destination}", mediaStreamIndex, id, oldSubtitleCachePath, newSubtitleCachePath); + + modified = true; + } + } + } + } + +#pragma warning disable CA1309 // Use ordinal string comparison + var attachments = context.AttachmentStreamInfos.Where(a => a.ItemId.Equals(id) && !string.Equals(a.Codec, "mjpeg")).ToList(); +#pragma warning restore CA1309 // Use ordinal string comparison + var shouldExtractOneByOne = attachments.Any(a => !string.IsNullOrEmpty(a.Filename) + && (a.Filename.Contains('/', StringComparison.OrdinalIgnoreCase) || a.Filename.Contains('\\', StringComparison.OrdinalIgnoreCase))); + foreach (var attachment in attachments) + { + var attachmentIndex = attachment.Index; + var oldAttachmentPath = GetOldAttachmentDataPath(path, attachmentIndex); + if (string.IsNullOrEmpty(oldAttachmentPath) || !File.Exists(oldAttachmentPath)) + { + oldAttachmentPath = GetOldAttachmentCachePath(itemIdString, attachment, shouldExtractOneByOne); + if (string.IsNullOrEmpty(oldAttachmentPath) || !File.Exists(oldAttachmentPath)) + { + continue; + } + } + + var newAttachmentPath = _pathManager.GetAttachmentPath(itemIdString, attachment.Filename ?? attachmentIndex.ToString(CultureInfo.InvariantCulture)); + if (File.Exists(newAttachmentPath)) + { + File.Delete(oldAttachmentPath); + } + else + { + var newDirectory = Path.GetDirectoryName(newAttachmentPath); + if (newDirectory is not null) + { + Directory.CreateDirectory(newDirectory); + File.Move(oldAttachmentPath, newAttachmentPath, false); + _logger.LogDebug("Moved attachment {Index} for {Item} from {Source} to {Destination}", attachmentIndex, id, oldAttachmentPath, newAttachmentPath); + + modified = true; + } + } + } + + return modified; + } + + private string? GetOldAttachmentDataPath(string? mediaPath, int attachmentStreamIndex) + { + if (mediaPath is null) + { + return null; + } + + string filename; + if (_fileSystem.IsPathFile(mediaPath)) + { + DateTime? date; + try + { + date = File.GetLastWriteTimeUtc(mediaPath); + } + catch (IOException e) + { + _logger.LogDebug("Skipping attachment at index {Index} for {Path}: {Exception}", attachmentStreamIndex, mediaPath, e.Message); + + return null; + } + catch (UnauthorizedAccessException e) + { + _logger.LogDebug("Skipping subtitle at index {Index} for {Path}: {Exception}", attachmentStreamIndex, mediaPath, e.Message); + + return null; + } + catch (ArgumentOutOfRangeException e) + { + _logger.LogDebug("Skipping attachment at index {Index} for {Path}: {Exception}", attachmentStreamIndex, mediaPath, e.Message); + + return null; + } + + filename = (mediaPath + attachmentStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Value.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("D", CultureInfo.InvariantCulture); + } + else + { + filename = (mediaPath + attachmentStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("D", CultureInfo.InvariantCulture); + } + + return Path.Join(_appPaths.DataPath, "attachments", filename[..1], filename); + } + + private string? GetOldAttachmentCachePath(string mediaSourceId, AttachmentStreamInfo attachment, bool shouldExtractOneByOne) + { + var attachmentFolderPath = Path.Join(_appPaths.CachePath, "attachments", mediaSourceId); + if (shouldExtractOneByOne) + { + return Path.Join(attachmentFolderPath, attachment.Index.ToString(CultureInfo.InvariantCulture)); + } + + if (string.IsNullOrEmpty(attachment.Filename)) + { + return null; + } + + return Path.Join(attachmentFolderPath, attachment.Filename); + } + + private string? GetOldSubtitleCachePath(string? path, int streamIndex, string outputSubtitleExtension) + { + if (path is null) + { + return null; + } + + DateTime? date; + try + { + date = File.GetLastWriteTimeUtc(path); + } + catch (ArgumentOutOfRangeException e) + { + _logger.LogDebug("Skipping subtitle at index {Index} for {Path}: {Exception}", streamIndex, path, e.Message); + + return null; + } + catch (UnauthorizedAccessException e) + { + _logger.LogDebug("Skipping subtitle at index {Index} for {Path}: {Exception}", streamIndex, path, e.Message); + + return null; + } + catch (IOException e) + { + _logger.LogDebug("Skipping subtitle at index {Index} for {Path}: {Exception}", streamIndex, path, e.Message); + + return null; + } + + var ticksParam = string.Empty; + ReadOnlySpan<char> filename = new Guid(MD5.HashData(Encoding.Unicode.GetBytes(path + "_" + streamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Value.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam))) + outputSubtitleExtension; + + return Path.Join(SubtitleCachePath, filename[..1], filename); + } + + private static string GetSubtitleExtension(string codec) + { + if (codec.ToLower(CultureInfo.InvariantCulture).Equals("ass", StringComparison.OrdinalIgnoreCase) + || codec.ToLower(CultureInfo.InvariantCulture).Equals("ssa", StringComparison.OrdinalIgnoreCase)) + { + return "." + codec; + } + else if (codec.Contains("pgs", StringComparison.OrdinalIgnoreCase)) + { + return ".sup"; + } + else + { + return ".srt"; + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs b/Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs new file mode 100644 index 00000000..0f55465e --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs @@ -0,0 +1,129 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using Jellyfin.Data.Enums; +using Jellyfin.Server.ServerSetupApp; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Trickplay; +using MediaBrowser.Model.IO; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Migration to move trickplay files to the new directory. +/// </summary> +#pragma warning disable CS0618 // Type or member is obsolete +[JellyfinMigration("2025-04-20T23:00:00", nameof(MoveTrickplayFiles), RunMigrationOnSetup = true)] +public class MoveTrickplayFiles : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete +{ + private readonly ITrickplayManager _trickplayManager; + private readonly IFileSystem _fileSystem; + private readonly ILibraryManager _libraryManager; + private readonly IStartupLogger _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="MoveTrickplayFiles"/> class. + /// </summary> + /// <param name="trickplayManager">Instance of the <see cref="ITrickplayManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="logger">The logger.</param> + public MoveTrickplayFiles( + ITrickplayManager trickplayManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IStartupLogger<MoveTrickplayFiles> logger) + { + _trickplayManager = trickplayManager; + _fileSystem = fileSystem; + _libraryManager = libraryManager; + _logger = logger; + } + + /// <inheritdoc /> + public void Perform() + { + const int Limit = 5000; + int itemCount = 0, offset = 0, previousCount; + + var sw = Stopwatch.StartNew(); + var trickplayQuery = new InternalItemsQuery + { + MediaTypes = [MediaType.Video], + SourceTypes = [SourceType.Library], + IsVirtualItem = false, + IsFolder = false + }; + + do + { + var trickplayInfos = _trickplayManager.GetTrickplayItemsAsync(Limit, offset).GetAwaiter().GetResult(); + trickplayQuery.ItemIds = trickplayInfos.Select(i => i.ItemId).Distinct().ToArray(); + var items = _libraryManager.GetItemList(trickplayQuery); + foreach (var trickplayInfo in trickplayInfos) + { + var item = items.OfType<Video>().FirstOrDefault(i => i.Id.Equals(trickplayInfo.ItemId)); + if (item is null) + { + continue; + } + + var moved = false; + var oldPath = GetOldTrickplayDirectory(item, trickplayInfo.Width); + var newPath = _trickplayManager.GetTrickplayDirectory(item, trickplayInfo.TileWidth, trickplayInfo.TileHeight, trickplayInfo.Width, false); + if (_fileSystem.DirectoryExists(oldPath)) + { + _fileSystem.MoveDirectory(oldPath, newPath); + moved = true; + } + + oldPath = GetNewOldTrickplayDirectory(item, trickplayInfo.TileWidth, trickplayInfo.TileHeight, trickplayInfo.Width, false); + if (_fileSystem.DirectoryExists(oldPath)) + { + _fileSystem.MoveDirectory(oldPath, newPath); + moved = true; + } + + if (moved) + { + itemCount++; + } + } + + offset += Limit; + previousCount = trickplayInfos.Count; + + _logger.LogInformation("Checked: {Checked} - Moved: {Count} - Time: {Time}", offset, itemCount, sw.Elapsed); + } while (previousCount == Limit); + + _logger.LogInformation("Moved {Count} items in {Time}", itemCount, sw.Elapsed); + } + + private string GetOldTrickplayDirectory(BaseItem item, int? width = null) + { + var path = Path.Combine(item.GetInternalMetadataPath(), "trickplay"); + + return width.HasValue ? Path.Combine(path, width.Value.ToString(CultureInfo.InvariantCulture)) : path; + } + + private string GetNewOldTrickplayDirectory(BaseItem item, int tileWidth, int tileHeight, int width, bool saveWithMedia = false) + { + var path = saveWithMedia + ? Path.Combine(item.ContainingFolderPath, Path.ChangeExtension(item.Path, ".trickplay")) + : Path.Combine(item.GetInternalMetadataPath(), "trickplay"); + + var subdirectory = string.Format( + CultureInfo.InvariantCulture, + "{0} - {1}x{2}", + width.ToString(CultureInfo.InvariantCulture), + tileWidth.ToString(CultureInfo.InvariantCulture), + tileHeight.ToString(CultureInfo.InvariantCulture)); + + return Path.Combine(path, subdirectory); + } +} diff --git a/Jellyfin.Server/Migrations/Routines/ReaddDefaultPluginRepository.cs b/Jellyfin.Server/Migrations/Routines/ReaddDefaultPluginRepository.cs new file mode 100644 index 00000000..ebf4a278 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/ReaddDefaultPluginRepository.cs @@ -0,0 +1,42 @@ +using System; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Updates; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Migration to initialize system configuration with the default plugin repository. +/// </summary> +#pragma warning disable CS0618 // Type or member is obsolete +[JellyfinMigration("2025-04-20T11:00:00", nameof(ReaddDefaultPluginRepository), "5F86E7F6-D966-4C77-849D-7A7B40B68C4E", RunMigrationOnSetup = true)] +public class ReaddDefaultPluginRepository : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete +{ + private readonly IServerConfigurationManager _serverConfigurationManager; + + private readonly RepositoryInfo _defaultRepositoryInfo = new RepositoryInfo + { + Name = "Jellyfin Stable", + Url = "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" + }; + + /// <summary> + /// Initializes a new instance of the <see cref="ReaddDefaultPluginRepository"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + public ReaddDefaultPluginRepository(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// <inheritdoc/> + public void Perform() + { + // Only add if repository list is empty + if (_serverConfigurationManager.Configuration.PluginRepositories.Length == 0) + { + _serverConfigurationManager.Configuration.PluginRepositories = new[] { _defaultRepositoryInfo }; + _serverConfigurationManager.SaveConfiguration(); + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/RefreshCleanNames.cs b/Jellyfin.Server/Migrations/Routines/RefreshCleanNames.cs new file mode 100644 index 00000000..eadabf67 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/RefreshCleanNames.cs @@ -0,0 +1,105 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Extensions; +using Jellyfin.Server.Implementations.Item; +using Jellyfin.Server.ServerSetupApp; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Migration to refresh CleanName values for all library items. +/// </summary> +[JellyfinMigration("2025-10-08T12:00:00", nameof(RefreshCleanNames))] +[JellyfinMigrationBackup(JellyfinDb = true)] +public class RefreshCleanNames : IAsyncMigrationRoutine +{ + private readonly IStartupLogger<RefreshCleanNames> _logger; + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + + /// <summary> + /// Initializes a new instance of the <see cref="RefreshCleanNames"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="dbProvider">Instance of the <see cref="IDbContextFactory{JellyfinDbContext}"/> interface.</param> + public RefreshCleanNames( + IStartupLogger<RefreshCleanNames> logger, + IDbContextFactory<JellyfinDbContext> dbProvider) + { + _logger = logger; + _dbProvider = dbProvider; + } + + /// <inheritdoc /> + public async Task PerformAsync(CancellationToken cancellationToken) + { + const int Limit = 1000; + int itemCount = 0; + + var sw = Stopwatch.StartNew(); + + using var context = _dbProvider.CreateDbContext(); + var records = context.BaseItems.Count(b => !string.IsNullOrEmpty(b.Name)); + _logger.LogInformation("Refreshing CleanName for {Count} library items", records); + + var processedInPartition = 0; + + await foreach (var item in context.BaseItems + .Where(b => !string.IsNullOrEmpty(b.Name)) + .OrderBy(e => e.Id) + .WithPartitionProgress((partition) => _logger.LogInformation("Processed: {Offset}/{Total} - Updated: {UpdatedCount} - Time: {Elapsed}", partition * Limit, records, itemCount, sw.Elapsed)) + .PartitionEagerAsync(Limit, cancellationToken) + .WithCancellation(cancellationToken) + .ConfigureAwait(false)) + { + try + { + var newCleanName = string.IsNullOrWhiteSpace(item.Name) ? string.Empty : BaseItemRepository.GetCleanValue(item.Name); + if (!string.Equals(newCleanName, item.CleanName, StringComparison.Ordinal)) + { + _logger.LogDebug( + "Updating CleanName for item {Id}: '{OldValue}' -> '{NewValue}'", + item.Id, + item.CleanName, + newCleanName); + item.CleanName = newCleanName; + itemCount++; + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to update CleanName for item {Id} ({Name})", item.Id, item.Name); + } + + processedInPartition++; + + if (processedInPartition >= Limit) + { + await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + // Clear tracked entities to avoid memory growth across partitions + context.ChangeTracker.Clear(); + processedInPartition = 0; + } + } + + // Save any remaining changes after the loop + if (processedInPartition > 0) + { + await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + context.ChangeTracker.Clear(); + } + + _logger.LogInformation( + "Refreshed CleanName for {UpdatedCount} out of {TotalCount} items in {Time}", + itemCount, + records, + sw.Elapsed); + } +} diff --git a/Jellyfin.Server/Migrations/Routines/RefreshInternalDateModified.cs b/Jellyfin.Server/Migrations/Routines/RefreshInternalDateModified.cs new file mode 100644 index 00000000..b23a7dbc --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/RefreshInternalDateModified.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Extensions; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.IO; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Migration to re-read creation dates for library items with internal metadata paths. +/// </summary> +[JellyfinMigration("2025-04-20T23:00:00", nameof(RefreshInternalDateModified))] +public class RefreshInternalDateModified : IDatabaseMigrationRoutine +{ + private readonly ILogger<RefreshInternalDateModified> _logger; + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + private readonly IFileSystem _fileSystem; + private readonly IServerApplicationHost _applicationHost; + private readonly bool _useFileCreationTimeForDateAdded; + + private IReadOnlyList<string> _internalTypes = [ + typeof(Genre).FullName!, + typeof(MusicGenre).FullName!, + typeof(MusicArtist).FullName!, + typeof(People).FullName!, + typeof(Studio).FullName! + ]; + + private IReadOnlyList<string> _internalPaths; + + /// <summary> + /// Initializes a new instance of the <see cref="RefreshInternalDateModified"/> class. + /// </summary> + /// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param> + /// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param> + /// <param name="configurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="dbProvider">Instance of the <see cref="IDbContextFactory{JellyfinDbContext}"/> interface.</param> + /// <param name="logger">The logger.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + public RefreshInternalDateModified( + IServerApplicationHost applicationHost, + IServerApplicationPaths applicationPaths, + IServerConfigurationManager configurationManager, + IDbContextFactory<JellyfinDbContext> dbProvider, + ILogger<RefreshInternalDateModified> logger, + IFileSystem fileSystem) + { + _dbProvider = dbProvider; + _logger = logger; + _fileSystem = fileSystem; + _applicationHost = applicationHost; + _internalPaths = [ + applicationPaths.ArtistsPath, + applicationPaths.GenrePath, + applicationPaths.MusicGenrePath, + applicationPaths.StudioPath, + applicationPaths.PeoplePath + ]; + _useFileCreationTimeForDateAdded = configurationManager.GetMetadataConfiguration().UseFileCreationTimeForDateAdded; + } + + /// <inheritdoc /> + public void Perform() + { + const int Limit = 5000; + int itemCount = 0, offset = 0; + + var sw = Stopwatch.StartNew(); + + using var context = _dbProvider.CreateDbContext(); + var records = context.BaseItems.Count(b => _internalTypes.Contains(b.Type)); + _logger.LogInformation("Checking if {Count} potentially internal items require refreshed DateModified", records); + + do + { + var results = context.BaseItems + .Where(b => _internalTypes.Contains(b.Type)) + .OrderBy(e => e.Id) + .Skip(offset) + .Take(Limit) + .ToList(); + + foreach (var item in results) + { + var itemPath = item.Path; + if (itemPath is not null) + { + var realPath = _applicationHost.ExpandVirtualPath(item.Path); + if (_internalPaths.Any(path => realPath.StartsWith(path, StringComparison.Ordinal))) + { + var writeTime = _fileSystem.GetLastWriteTimeUtc(realPath); + var itemModificationTime = item.DateModified; + if (writeTime != itemModificationTime) + { + _logger.LogDebug("Reset file modification date: Old: {Old} - New: {New} - Path: {Path}", itemModificationTime, writeTime, realPath); + item.DateModified = writeTime; + if (_useFileCreationTimeForDateAdded) + { + item.DateCreated = _fileSystem.GetCreationTimeUtc(realPath); + } + + itemCount++; + } + } + } + } + + offset += Limit; + if (offset > records) + { + offset = records; + } + + _logger.LogInformation("Checked: {Count} - Refreshed: {Items} - Time: {Time}", offset, itemCount, sw.Elapsed); + } while (offset < records); + + context.SaveChanges(); + + _logger.LogInformation("Refreshed DateModified for {Count} items in {Time}", itemCount, sw.Elapsed); + } +} diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs b/Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs new file mode 100644 index 00000000..b626c473 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs @@ -0,0 +1,45 @@ +using System; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Removes the old 'RemoveDownloadImagesInAdvance' from library options. +/// </summary> +#pragma warning disable CS0618 // Type or member is obsolete +[JellyfinMigration("2025-04-20T13:00:00", nameof(RemoveDownloadImagesInAdvance), "A81F75E0-8F43-416F-A5E8-516CCAB4D8CC")] +internal class RemoveDownloadImagesInAdvance : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete +{ + private readonly ILogger<RemoveDownloadImagesInAdvance> _logger; + private readonly ILibraryManager _libraryManager; + + public RemoveDownloadImagesInAdvance(ILogger<RemoveDownloadImagesInAdvance> logger, ILibraryManager libraryManager) + { + _logger = logger; + _libraryManager = libraryManager; + } + + /// <inheritdoc/> + public void Perform() + { + var virtualFolders = _libraryManager.GetVirtualFolders(false); + _logger.LogInformation("Removing 'RemoveDownloadImagesInAdvance' settings in all the libraries"); + foreach (var virtualFolder in virtualFolders) + { + // Some virtual folders don't have a proper item id. + if (!Guid.TryParse(virtualFolder.ItemId, out var folderId)) + { + continue; + } + + var libraryOptions = virtualFolder.LibraryOptions; + var collectionFolder = _libraryManager.GetItemById<CollectionFolder>(folderId) ?? throw new InvalidOperationException("Failed to find CollectionFolder"); + // The property no longer exists in LibraryOptions, so we just re-save the options to get old data removed. + collectionFolder.UpdateLibraryOptions(libraryOptions); + _logger.LogInformation("Removed from '{VirtualFolder}'", virtualFolder.Name); + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs new file mode 100644 index 00000000..c9e66d0c --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs @@ -0,0 +1,76 @@ +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using Emby.Server.Implementations.Data; +using MediaBrowser.Controller; +using Microsoft.Data.Sqlite; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Remove duplicate entries which were caused by a bug where a file was considered to be an "Extra" to itself. +/// </summary> +#pragma warning disable CS0618 // Type or member is obsolete +[JellyfinMigration("2025-04-20T08:00:00", nameof(RemoveDuplicateExtras), "ACBE17B7-8435-4A83-8B64-6FCF162CB9BD")] +internal class RemoveDuplicateExtras : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete +{ + private const string DbFilename = "library.db"; + private readonly ILogger<RemoveDuplicateExtras> _logger; + private readonly IServerApplicationPaths _paths; + + public RemoveDuplicateExtras(ILogger<RemoveDuplicateExtras> logger, IServerApplicationPaths paths) + { + _logger = logger; + _paths = paths; + } + + /// <inheritdoc/> + public void Perform() + { + var dataPath = _paths.DataPath; + var dbPath = Path.Combine(dataPath, DbFilename); + using var connection = new SqliteConnection($"Filename={dbPath}"); + connection.Open(); + using (var transaction = connection.BeginTransaction()) + { + // Query the database for the ids of duplicate extras + var queryResult = connection.Query("SELECT t1.Path FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video'"); + var bads = string.Join(", ", queryResult.Select(x => x.GetString(0))); + + // Do nothing if no duplicate extras were detected + if (bads.Length == 0) + { + _logger.LogInformation("No duplicate extras detected, skipping migration."); + return; + } + + // Back up the database before deleting any entries + for (int i = 1; ; i++) + { + var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i); + if (!File.Exists(bakPath)) + { + try + { + File.Copy(dbPath, bakPath); + _logger.LogInformation("Library database backed up to {BackupPath}", bakPath); + break; + } + catch (Exception ex) + { + _logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath); + throw; + } + } + } + + // Delete all duplicate extras + _logger.LogInformation("Removing found duplicated extras for the following items: {DuplicateExtras}", bads); + connection.Execute("DELETE FROM TypedBaseItems WHERE rowid IN (SELECT t1.rowid FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video')"); + transaction.Commit(); + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs b/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs new file mode 100644 index 00000000..23f21242 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs @@ -0,0 +1,58 @@ +using System; +using System.Linq; +using System.Threading; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Remove duplicate playlist entries. +/// </summary> +#pragma warning disable CS0618 // Type or member is obsolete +[JellyfinMigration("2025-04-20T19:00:00", nameof(RemoveDuplicatePlaylistChildren), "96C156A2-7A13-4B3B-A8B8-FB80C94D20C0")] +internal class RemoveDuplicatePlaylistChildren : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete +{ + private readonly ILibraryManager _libraryManager; + private readonly IPlaylistManager _playlistManager; + + public RemoveDuplicatePlaylistChildren( + ILibraryManager libraryManager, + IPlaylistManager playlistManager) + { + _libraryManager = libraryManager; + _playlistManager = playlistManager; + } + + /// <inheritdoc/> + public void Perform() + { + var playlists = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = [BaseItemKind.Playlist] + }) + .Cast<Playlist>() + .Where(p => !p.OpenAccess || !p.OwnerUserId.Equals(Guid.Empty)) + .ToArray(); + + if (playlists.Length > 0) + { + foreach (var playlist in playlists) + { + var linkedChildren = playlist.LinkedChildren; + if (linkedChildren.Length > 0) + { + var nullItemChildren = linkedChildren.Where(c => c.ItemId is null); + var deduplicatedChildren = linkedChildren.DistinctBy(c => c.ItemId); + var newLinkedChildren = nullItemChildren.Concat(deduplicatedChildren); + playlist.LinkedChildren = linkedChildren; + playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult(); + _playlistManager.SavePlaylistFile(playlist); + } + } + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/ReseedFolderFlag.cs b/Jellyfin.Server/Migrations/Routines/ReseedFolderFlag.cs new file mode 100644 index 00000000..502763ac --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/ReseedFolderFlag.cs @@ -0,0 +1,74 @@ +#pragma warning disable RS0030 // Do not use banned APIs +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Implementations.Data; +using Jellyfin.Database.Implementations; +using Jellyfin.Server.ServerSetupApp; +using MediaBrowser.Controller; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +[JellyfinMigration("2025-07-30T21:50:00", nameof(ReseedFolderFlag))] +[JellyfinMigrationBackup(JellyfinDb = true)] +internal class ReseedFolderFlag : IAsyncMigrationRoutine +{ + private const string DbFilename = "library.db.old"; + + private readonly IStartupLogger _logger; + private readonly IServerApplicationPaths _paths; + private readonly IDbContextFactory<JellyfinDbContext> _provider; + + public ReseedFolderFlag( + IStartupLogger<MigrateLibraryDb> startupLogger, + IDbContextFactory<JellyfinDbContext> provider, + IServerApplicationPaths paths) + { + _logger = startupLogger; + _provider = provider; + _paths = paths; + } + + internal static bool RerunGuardFlag { get; set; } = false; + + public async Task PerformAsync(CancellationToken cancellationToken) + { + if (RerunGuardFlag) + { + _logger.LogInformation("Migration is skipped because it does not apply."); + return; + } + + _logger.LogInformation("Migrating the IsFolder flag from library.db.old may take a while, do not stop Jellyfin."); + + var dataPath = _paths.DataPath; + var libraryDbPath = Path.Combine(dataPath, DbFilename); + if (!File.Exists(libraryDbPath)) + { + _logger.LogError("Cannot migrate IsFolder flag from {LibraryDb} as it does not exist. This migration expects the MigrateLibraryDb to run first.", libraryDbPath); + return; + } + + var dbContext = await _provider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + using var connection = new SqliteConnection($"Filename={libraryDbPath};Mode=ReadOnly"); + var queryResult = connection.Query( + """ + SELECT guid FROM TypedBaseItems + WHERE IsFolder = true + """) + .Select(entity => entity.GetGuid(0)) + .ToList(); + _logger.LogInformation("Migrating the IsFolder flag for {Count} items.", queryResult.Count); + foreach (var id in queryResult) + { + await dbContext.BaseItems.Where(e => e.Id == id).ExecuteUpdateAsync(e => e.SetProperty(f => f.IsFolder, true), cancellationToken).ConfigureAwait(false); + } + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/UpdateDefaultPluginRepository.cs b/Jellyfin.Server/Migrations/Routines/UpdateDefaultPluginRepository.cs new file mode 100644 index 00000000..f58cf274 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/UpdateDefaultPluginRepository.cs @@ -0,0 +1,46 @@ +using System; +using MediaBrowser.Controller.Configuration; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Migration to update the default Jellyfin plugin repository. +/// </summary> +#pragma warning disable CS0618 // Type or member is obsolete +[JellyfinMigration("2025-04-20T17:00:00", nameof(UpdateDefaultPluginRepository), "852816E0-2712-49A9-9240-C6FC5FCAD1A8", RunMigrationOnSetup = true)] +public class UpdateDefaultPluginRepository : IMigrationRoutine +#pragma warning restore CS0618 // Type or member is obsolete +{ + private const string NewRepositoryUrl = "https://repo.jellyfin.org/files/plugin/manifest.json"; + private const string OldRepositoryUrl = "https://repo.jellyfin.org/releases/plugin/manifest-stable.json"; + + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="UpdateDefaultPluginRepository"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + public UpdateDefaultPluginRepository(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// <inheritdoc /> + public void Perform() + { + var updated = false; + foreach (var repo in _serverConfigurationManager.Configuration.PluginRepositories) + { + if (string.Equals(repo.Url, OldRepositoryUrl, StringComparison.OrdinalIgnoreCase)) + { + repo.Url = NewRepositoryUrl; + updated = true; + } + } + + if (updated) + { + _serverConfigurationManager.SaveConfiguration(); + } + } +} diff --git a/Jellyfin.Server/Migrations/Stages/CodeMigration.cs b/Jellyfin.Server/Migrations/Stages/CodeMigration.cs new file mode 100644 index 00000000..971b4760 --- /dev/null +++ b/Jellyfin.Server/Migrations/Stages/CodeMigration.cs @@ -0,0 +1,87 @@ +using System; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Server.ServerSetupApp; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Stages; + +internal class CodeMigration(Type migrationType, JellyfinMigrationAttribute metadata, JellyfinMigrationBackupAttribute? migrationBackupAttribute) +{ + public Type MigrationType { get; } = migrationType; + + public JellyfinMigrationAttribute Metadata { get; } = metadata; + + public JellyfinMigrationBackupAttribute? BackupRequirements { get; set; } = migrationBackupAttribute; + + public string BuildCodeMigrationId() + { + return Metadata.Order.ToString("yyyyMMddHHmmsss", CultureInfo.InvariantCulture) + "_" + Metadata.Name!; + } + + private IServiceCollection MigrationServices(IServiceProvider serviceProvider, IStartupLogger logger) + { + var childServiceCollection = new ServiceCollection() + .AddSingleton(serviceProvider) + .AddSingleton(logger) + .AddSingleton(typeof(IStartupLogger<>), typeof(NestedStartupLogger<>)) + .AddSingleton<StartupLogTopic>(logger.Topic!); + + foreach (ServiceDescriptor service in serviceProvider.GetRequiredService<IServiceCollection>()) + { + if (service.Lifetime == ServiceLifetime.Singleton && !service.ServiceType.IsGenericTypeDefinition) + { + childServiceCollection.AddSingleton(service.ServiceType, _ => serviceProvider.GetService(service.ServiceType)!); + continue; + } + + childServiceCollection.Add(service); + } + + return childServiceCollection; + } + + public async Task Perform(IServiceProvider? serviceProvider, IStartupLogger logger, CancellationToken cancellationToken) + { +#pragma warning disable CS0618 // Type or member is obsolete + if (typeof(IMigrationRoutine).IsAssignableFrom(MigrationType)) + { + if (serviceProvider is null) + { + ((IMigrationRoutine)Activator.CreateInstance(MigrationType)!).Perform(); + } + else + { + using var migrationServices = MigrationServices(serviceProvider, logger).BuildServiceProvider(); + ((IMigrationRoutine)ActivatorUtilities.CreateInstance(migrationServices, MigrationType)).Perform(); +#pragma warning restore CS0618 // Type or member is obsolete + } + } + else if (typeof(IAsyncMigrationRoutine).IsAssignableFrom(MigrationType)) + { + if (serviceProvider is null) + { + await ((IAsyncMigrationRoutine)Activator.CreateInstance(MigrationType)!).PerformAsync(cancellationToken).ConfigureAwait(false); + } + else + { + using var migrationServices = MigrationServices(serviceProvider, logger).BuildServiceProvider(); + await ((IAsyncMigrationRoutine)ActivatorUtilities.CreateInstance(migrationServices, MigrationType)).PerformAsync(cancellationToken).ConfigureAwait(false); + } + } + else + { + throw new InvalidOperationException($"The type {MigrationType} does not implement either IMigrationRoutine or IAsyncMigrationRoutine and is not a valid migration type"); + } + } + + private class NestedStartupLogger<TCategory> : StartupLogger<TCategory> + { + public NestedStartupLogger(ILogger logger, StartupLogTopic topic) : base(logger, topic) + { + } + } +} diff --git a/Jellyfin.Server/Migrations/Stages/JellyfinMigrationStageTypes.cs b/Jellyfin.Server/Migrations/Stages/JellyfinMigrationStageTypes.cs new file mode 100644 index 00000000..3d5ec233 --- /dev/null +++ b/Jellyfin.Server/Migrations/Stages/JellyfinMigrationStageTypes.cs @@ -0,0 +1,26 @@ +namespace Jellyfin.Server.Migrations.Stages; + +/// <summary> +/// Defines the stages the <see cref="JellyfinMigrationService"/> supports. +/// </summary> +#pragma warning disable CA1008 // Enums should have zero value +public enum JellyfinMigrationStageTypes +#pragma warning restore CA1008 // Enums should have zero value +{ + /// <summary> + /// Runs before services are initialised. + /// Reserved for migrations that are modifying the application server itself. Should be avoided if possible. + /// </summary> + PreInitialisation = 1, + + /// <summary> + /// Runs after the host has been configured and includes the database migrations. + /// Allows the mix order of migrations that contain application code and database changes. + /// </summary> + CoreInitialisation = 2, + + /// <summary> + /// Runs after services has been registered and initialised. Last step before running the server. + /// </summary> + AppInitialisation = 3 +} diff --git a/Jellyfin.Server/Migrations/Stages/MigrationStage.cs b/Jellyfin.Server/Migrations/Stages/MigrationStage.cs new file mode 100644 index 00000000..efcadbf0 --- /dev/null +++ b/Jellyfin.Server/Migrations/Stages/MigrationStage.cs @@ -0,0 +1,16 @@ +using System.Collections.ObjectModel; + +namespace Jellyfin.Server.Migrations.Stages; + +/// <summary> +/// Defines a Stage that can be Invoked and Handled at different times from the code. +/// </summary> +internal class MigrationStage : Collection<CodeMigration> +{ + public MigrationStage(JellyfinMigrationStageTypes stage) + { + Stage = stage; + } + + public JellyfinMigrationStageTypes Stage { get; } +} diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs new file mode 100644 index 00000000..93f71fdc --- /dev/null +++ b/Jellyfin.Server/Program.cs @@ -0,0 +1,357 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using CommandLine; +using Emby.Server.Implementations; +using Emby.Server.Implementations.Configuration; +using Emby.Server.Implementations.Serialization; +using Jellyfin.Database.Implementations; +using Jellyfin.Server.Extensions; +using Jellyfin.Server.Helpers; +using Jellyfin.Server.Implementations.DatabaseConfiguration; +using Jellyfin.Server.Implementations.Extensions; +using Jellyfin.Server.Implementations.StorageHelpers; +using Jellyfin.Server.Implementations.SystemBackupService; +using Jellyfin.Server.Migrations; +using Jellyfin.Server.Migrations.Stages; +using Jellyfin.Server.ServerSetupApp; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; +using Microsoft.AspNetCore.Hosting; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Serilog; +using Serilog.Extensions.Logging; +using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace Jellyfin.Server +{ + /// <summary> + /// Class containing the entry point of the application. + /// </summary> + public static class Program + { + /// <summary> + /// The name of logging configuration file containing application defaults. + /// </summary> + public const string LoggingConfigFileDefault = "logging.default.json"; + + /// <summary> + /// The name of the logging configuration file containing the system-specific override settings. + /// </summary> + public const string LoggingConfigFileSystem = "logging.json"; + + private static readonly SerilogLoggerFactory _loggerFactory = new SerilogLoggerFactory(); + private static SetupServer? _setupServer; + private static CoreAppHost? _appHost; + private static IHost? _jellyfinHost = null; + private static long _startTimestamp; + private static ILogger _logger = NullLogger.Instance; + private static bool _restartOnShutdown; + private static IStartupLogger<JellyfinMigrationService>? _migrationLogger; + private static string? _restoreFromBackup; + + /// <summary> + /// The entry point of the application. + /// </summary> + /// <param name="args">The command line arguments passed.</param> + /// <returns><see cref="Task" />.</returns> + public static Task Main(string[] args) + { + static Task ErrorParsingArguments(IEnumerable<Error> errors) + { + Environment.ExitCode = 1; + return Task.CompletedTask; + } + + // Parse the command line arguments and either start the app or exit indicating error + return Parser.Default.ParseArguments<StartupOptions>(args) + .MapResult(StartApp, ErrorParsingArguments); + } + + private static async Task StartApp(StartupOptions options) + { + _restoreFromBackup = options.RestoreArchive; + _startTimestamp = Stopwatch.GetTimestamp(); + ServerApplicationPaths appPaths = StartupHelpers.CreateApplicationPaths(options); + appPaths.MakeSanityCheckOrThrow(); + + // $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager + Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath); + + // Enable cl-va P010 interop for tonemapping on Intel VAAPI + Environment.SetEnvironmentVariable("NEOReadDebugKeys", "1"); + Environment.SetEnvironmentVariable("EnableExtendedVaFormats", "1"); + + await StartupHelpers.InitLoggingConfigFile(appPaths).ConfigureAwait(false); + + // Create an instance of the application configuration to use for application startup + IConfiguration startupConfig = CreateAppConfiguration(options, appPaths); + StartupHelpers.InitializeLoggingFramework(startupConfig, appPaths); + _setupServer = new SetupServer(static () => _jellyfinHost?.Services?.GetService<INetworkManager>(), appPaths, static () => _appHost, _loggerFactory, startupConfig); + await _setupServer.RunAsync().ConfigureAwait(false); + _logger = _loggerFactory.CreateLogger("Main"); + StartupLogger.Logger = new StartupLogger(_logger); + + // Use the logging framework for uncaught exceptions instead of std error + AppDomain.CurrentDomain.UnhandledException += (_, e) + => _logger.LogCritical((Exception)e.ExceptionObject, "Unhandled Exception"); + + _logger.LogInformation( + "Jellyfin version: {Version}", + Assembly.GetEntryAssembly()!.GetName().Version!.ToString(3)); + + StartupHelpers.LogEnvironmentInfo(_logger, appPaths); + + // If hosting the web client, validate the client content path + if (startupConfig.HostWebClient()) + { + var webContentPath = appPaths.WebPath; + if (!Directory.Exists(webContentPath) || !Directory.EnumerateFiles(webContentPath).Any()) + { + _logger.LogError( + "The server is expected to host the web client, but the provided content directory is either " + + "invalid or empty: {WebContentPath}. If you do not want to host the web client with the " + + "server, you may set the '--nowebclient' command line flag, or set" + + "'{ConfigKey}=false' in your config settings", + webContentPath, + HostWebClientKey); + Environment.ExitCode = 1; + return; + } + } + + StorageHelper.TestCommonPathsForStorageCapacity(appPaths, StartupLogger.Logger.With(_loggerFactory.CreateLogger<Startup>()).BeginGroup($"Storage Check")); + + StartupHelpers.PerformStaticInitialization(); + + await ApplyStartupMigrationAsync(appPaths, startupConfig).ConfigureAwait(false); + + do + { + await StartServer(appPaths, options, startupConfig).ConfigureAwait(false); + + if (_restartOnShutdown) + { + _startTimestamp = Stopwatch.GetTimestamp(); + await _setupServer.StopAsync().ConfigureAwait(false); + await _setupServer.RunAsync().ConfigureAwait(false); + } + } while (_restartOnShutdown); + + _setupServer.Dispose(); + } + + private static async Task StartServer(IServerApplicationPaths appPaths, StartupOptions options, IConfiguration startupConfig) + { + using CoreAppHost appHost = new CoreAppHost( + appPaths, + _loggerFactory, + options, + startupConfig); + _appHost = appHost; + var configurationCompleted = false; + try + { + _jellyfinHost = Host.CreateDefaultBuilder() + .UseConsoleLifetime() + .ConfigureServices(services => appHost.Init(services)) + .ConfigureWebHostDefaults(webHostBuilder => + { + webHostBuilder.ConfigureWebHostBuilder(appHost, startupConfig, appPaths, _logger); + if (bool.TryParse(Environment.GetEnvironmentVariable("JELLYFIN_ENABLE_IIS"), out var iisEnabled) && iisEnabled) + { + _logger.LogCritical("UNSUPPORTED HOSTING ENVIRONMENT Microsoft Internet Information Services. The option to run Jellyfin on IIS is an unsupported and untested feature. Only use at your own discretion."); + webHostBuilder.UseIIS(); + } + }) + .ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(options, appPaths, startupConfig)) + .UseSerilog() + .ConfigureServices(e => e + .RegisterStartupLogger() + .AddSingleton<IServiceCollection>(e)) + .Build(); + + /* + * Initialize the transcode path marker so we avoid starting Jellyfin in a broken state. + * This should really be a part of IApplicationPaths but this path is configured differently. + */ + _ = appHost.ConfigurationManager.GetTranscodePath(); + + // Re-use the host service provider in the app host since ASP.NET doesn't allow a custom service collection. + appHost.ServiceProvider = _jellyfinHost.Services; + PrepareDatabaseProvider(appHost.ServiceProvider); + + if (!string.IsNullOrWhiteSpace(_restoreFromBackup)) + { + await appHost.ServiceProvider.GetService<IBackupService>()!.RestoreBackupAsync(_restoreFromBackup).ConfigureAwait(false); + _restoreFromBackup = null; + _restartOnShutdown = true; + return; + } + + var jellyfinMigrationService = ActivatorUtilities.CreateInstance<JellyfinMigrationService>(appHost.ServiceProvider); + await jellyfinMigrationService.PrepareSystemForMigration(_logger).ConfigureAwait(false); + await jellyfinMigrationService.MigrateStepAsync(JellyfinMigrationStageTypes.CoreInitialisation, appHost.ServiceProvider).ConfigureAwait(false); + + await appHost.InitializeServices(startupConfig).ConfigureAwait(false); + + await jellyfinMigrationService.MigrateStepAsync(JellyfinMigrationStageTypes.AppInitialisation, appHost.ServiceProvider).ConfigureAwait(false); + await jellyfinMigrationService.CleanupSystemAfterMigration(_logger).ConfigureAwait(false); + try + { + configurationCompleted = true; + await _setupServer!.StopAsync().ConfigureAwait(false); + await _jellyfinHost.StartAsync().ConfigureAwait(false); + + if (!OperatingSystem.IsWindows() && startupConfig.UseUnixSocket()) + { + var socketPath = StartupHelpers.GetUnixSocketPath(startupConfig, appPaths); + + StartupHelpers.SetUnixSocketPermissions(startupConfig, socketPath, _logger); + } + } + catch (Exception) + { + _logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in network.xml and try again"); + throw; + } + + await appHost.RunStartupTasksAsync().ConfigureAwait(false); + + _logger.LogInformation("Startup complete {Time:g}", Stopwatch.GetElapsedTime(_startTimestamp)); + + await _jellyfinHost.WaitForShutdownAsync().ConfigureAwait(false); + _restartOnShutdown = appHost.ShouldRestart; + _restoreFromBackup = appHost.RestoreBackupPath; + } + catch (Exception ex) + { + _restartOnShutdown = false; + _logger.LogCritical(ex, "Error while starting server"); + if (_setupServer!.IsAlive && !configurationCompleted) + { + _setupServer!.SoftStop(); + await Task.Delay(TimeSpan.FromMinutes(10)).ConfigureAwait(false); + await _setupServer!.StopAsync().ConfigureAwait(false); + } + } + finally + { + // Don't throw additional exception if startup failed. + if (appHost.ServiceProvider is not null) + { + _logger.LogInformation("Running query planner optimizations in the database... This might take a while"); + + var databaseProvider = appHost.ServiceProvider.GetRequiredService<IJellyfinDatabaseProvider>(); + using var shutdownSource = new CancellationTokenSource(); + shutdownSource.CancelAfter((int)TimeSpan.FromSeconds(60).TotalMicroseconds); + await databaseProvider.RunShutdownTask(shutdownSource.Token).ConfigureAwait(false); + } + + _appHost = null; + _jellyfinHost?.Dispose(); + } + } + + /// <summary> + /// [Internal]Runs the startup Migrations. + /// </summary> + /// <remarks> + /// Not intended to be used other then by jellyfin and its tests. + /// </remarks> + /// <param name="appPaths">Application Paths.</param> + /// <param name="startupConfig">Startup Config.</param> + /// <returns>A task.</returns> + public static async Task ApplyStartupMigrationAsync(ServerApplicationPaths appPaths, IConfiguration startupConfig) + { + _migrationLogger = StartupLogger.Logger.BeginGroup<JellyfinMigrationService>($"Migration Service"); + var startupConfigurationManager = new ServerConfigurationManager(appPaths, _loggerFactory, new MyXmlSerializer()); + startupConfigurationManager.AddParts([new DatabaseConfigurationFactory()]); + var migrationStartupServiceProvider = new ServiceCollection() + .AddLogging(d => d.AddSerilog()) + .AddJellyfinDbContext(startupConfigurationManager, startupConfig) + .AddSingleton<IApplicationPaths>(appPaths) + .AddSingleton<ServerApplicationPaths>(appPaths) + .RegisterStartupLogger(); + + migrationStartupServiceProvider.AddSingleton(migrationStartupServiceProvider); + var startupService = migrationStartupServiceProvider.BuildServiceProvider(); + + PrepareDatabaseProvider(startupService); + + var jellyfinMigrationService = ActivatorUtilities.CreateInstance<JellyfinMigrationService>(startupService); + await jellyfinMigrationService.CheckFirstTimeRunOrMigration(appPaths).ConfigureAwait(false); + await jellyfinMigrationService.MigrateStepAsync(Migrations.Stages.JellyfinMigrationStageTypes.PreInitialisation, startupService).ConfigureAwait(false); + } + + /// <summary> + /// [Internal]Runs the Jellyfin migrator service with the Core stage. + /// </summary> + /// <remarks> + /// Not intended to be used other then by jellyfin and its tests. + /// </remarks> + /// <param name="serviceProvider">The service provider.</param> + /// <param name="jellyfinMigrationStage">The stage to run.</param> + /// <returns>A task.</returns> + public static async Task ApplyCoreMigrationsAsync(IServiceProvider serviceProvider, Migrations.Stages.JellyfinMigrationStageTypes jellyfinMigrationStage) + { + var jellyfinMigrationService = ActivatorUtilities.CreateInstance<JellyfinMigrationService>(serviceProvider, _migrationLogger!); + await jellyfinMigrationService.MigrateStepAsync(jellyfinMigrationStage, serviceProvider).ConfigureAwait(false); + } + + /// <summary> + /// Create the application configuration. + /// </summary> + /// <param name="commandLineOpts">The command line options passed to the program.</param> + /// <param name="appPaths">The application paths.</param> + /// <returns>The application configuration.</returns> + public static IConfiguration CreateAppConfiguration(StartupOptions commandLineOpts, IApplicationPaths appPaths) + { + return new ConfigurationBuilder() + .ConfigureAppConfiguration(commandLineOpts, appPaths) + .Build(); + } + + private static IConfigurationBuilder ConfigureAppConfiguration( + this IConfigurationBuilder config, + StartupOptions commandLineOpts, + IApplicationPaths appPaths, + IConfiguration? startupConfig = null) + { + // Use the swagger API page as the default redirect path if not hosting the web client + var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration; + if (startupConfig is not null && !startupConfig.HostWebClient()) + { + inMemoryDefaultConfig[DefaultRedirectKey] = "api-docs/swagger"; + } + + return config + .SetBasePath(appPaths.ConfigurationDirectoryPath) + .AddInMemoryCollection(inMemoryDefaultConfig) + .AddJsonFile(LoggingConfigFileDefault, optional: false, reloadOnChange: true) + .AddJsonFile(LoggingConfigFileSystem, optional: true, reloadOnChange: true) + .AddEnvironmentVariables("JELLYFIN_") + .AddInMemoryCollection(commandLineOpts.ConvertToConfig()); + } + + private static void PrepareDatabaseProvider(IServiceProvider services) + { + var factory = services.GetRequiredService<IDbContextFactory<JellyfinDbContext>>(); + var provider = services.GetRequiredService<IJellyfinDatabaseProvider>(); + provider.DbContextFactory = factory; + } + } +} diff --git a/Jellyfin.Server/Properties/AssemblyInfo.cs b/Jellyfin.Server/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..fe2d5c5f --- /dev/null +++ b/Jellyfin.Server/Properties/AssemblyInfo.cs @@ -0,0 +1,24 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Jellyfin.Server")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Jellyfin Project")] +[assembly: AssemblyProduct("Jellyfin Server")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +[assembly: InternalsVisibleTo("Jellyfin.Server.Tests")] diff --git a/Jellyfin.Server/Properties/launchSettings.json b/Jellyfin.Server/Properties/launchSettings.json new file mode 100644 index 00000000..20d432af --- /dev/null +++ b/Jellyfin.Server/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "profiles": { + "Jellyfin.Server": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "http://localhost:8096", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Jellyfin.Server (nowebclient)": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "commandLineArgs": "--nowebclient" + }, + "Jellyfin.Server (API Docs)": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "api-docs/swagger", + "applicationUrl": "http://localhost:8096", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "commandLineArgs": "--nowebclient" + } + } +} diff --git a/Jellyfin.Server/Resources/Configuration/logging.json b/Jellyfin.Server/Resources/Configuration/logging.json new file mode 100644 index 00000000..ac5d9f60 --- /dev/null +++ b/Jellyfin.Server/Resources/Configuration/logging.json @@ -0,0 +1,38 @@ +{ + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "System": "Warning" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "outputTemplate": "[{Timestamp:HH:mm:ss.fff}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "Async", + "Args": { + "configure": [ + { + "Name": "File", + "Args": { + "path": "%JELLYFIN_LOG_DIR%//log_.log", + "rollingInterval": "Day", + "retainedFileCountLimit": 3, + "rollOnFileSizeLimit": true, + "fileSizeLimitBytes": 100000000, + "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}" + } + } + ] + } + } + ], + "Enrich": [ "FromLogContext", "WithThreadId" ] + } +} diff --git a/Jellyfin.Server/ServerSetupApp/IStartupLogger.cs b/Jellyfin.Server/ServerSetupApp/IStartupLogger.cs new file mode 100644 index 00000000..e7c19393 --- /dev/null +++ b/Jellyfin.Server/ServerSetupApp/IStartupLogger.cs @@ -0,0 +1,66 @@ +using System; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace Jellyfin.Server.ServerSetupApp; + +/// <summary> +/// Defines the Startup Logger. This logger acts an an aggregate logger that will push though all log messages to both the attached logger as well as the startup UI. +/// </summary> +public interface IStartupLogger : ILogger +{ + /// <summary> + /// Gets the topic this logger is assigned to. + /// </summary> + StartupLogTopic? Topic { get; } + + /// <summary> + /// Adds another logger instance to this logger for combined logging. + /// </summary> + /// <param name="logger">Other logger to rely messages to.</param> + /// <returns>A combined logger.</returns> + IStartupLogger With(ILogger logger); + + /// <summary> + /// Opens a new Group logger within the parent logger. + /// </summary> + /// <param name="logEntry">Defines the log message that introduces the new group.</param> + /// <returns>A new logger that can write to the group.</returns> + IStartupLogger BeginGroup(FormattableString logEntry); + + /// <summary> + /// Adds another logger instance to this logger for combined logging. + /// </summary> + /// <param name="logger">Other logger to rely messages to.</param> + /// <returns>A combined logger.</returns> + /// <typeparam name="TCategory">The logger cateogry.</typeparam> + IStartupLogger<TCategory> With<TCategory>(ILogger logger); + + /// <summary> + /// Opens a new Group logger within the parent logger. + /// </summary> + /// <param name="logEntry">Defines the log message that introduces the new group.</param> + /// <returns>A new logger that can write to the group.</returns> + /// <typeparam name="TCategory">The logger cateogry.</typeparam> + IStartupLogger<TCategory> BeginGroup<TCategory>(FormattableString logEntry); +} + +/// <summary> +/// Defines a logger that can be injected via DI to get a startup logger initialised with an logger framework connected <see cref="ILogger"/>. +/// </summary> +/// <typeparam name="TCategory">The logger cateogry.</typeparam> +public interface IStartupLogger<TCategory> : IStartupLogger +{ + /// <summary> + /// Adds another logger instance to this logger for combined logging. + /// </summary> + /// <param name="logger">Other logger to rely messages to.</param> + /// <returns>A combined logger.</returns> + new IStartupLogger<TCategory> With(ILogger logger); + + /// <summary> + /// Opens a new Group logger within the parent logger. + /// </summary> + /// <param name="logEntry">Defines the log message that introduces the new group.</param> + /// <returns>A new logger that can write to the group.</returns> + new IStartupLogger<TCategory> BeginGroup(FormattableString logEntry); +} diff --git a/Jellyfin.Server/ServerSetupApp/SetupServer.cs b/Jellyfin.Server/ServerSetupApp/SetupServer.cs new file mode 100644 index 00000000..4340969a --- /dev/null +++ b/Jellyfin.Server/ServerSetupApp/SetupServer.cs @@ -0,0 +1,376 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Implementations.Configuration; +using Emby.Server.Implementations.Serialization; +using Jellyfin.Networking.Manager; +using Jellyfin.Server.Extensions; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; +using Morestachio; +using Morestachio.Framework.IO.SingleStream; +using Morestachio.Rendering; +using Serilog; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace Jellyfin.Server.ServerSetupApp; + +/// <summary> +/// Creates a fake application pipeline that will only exist for as long as the main app is not started. +/// </summary> +public sealed class SetupServer : IDisposable +{ + private readonly Func<INetworkManager?> _networkManagerFactory; + private readonly IApplicationPaths _applicationPaths; + private readonly Func<IServerApplicationHost?> _serverFactory; + private readonly ILoggerFactory _loggerFactory; + private readonly IConfiguration _startupConfiguration; + private readonly ServerConfigurationManager _configurationManager; + private IRenderer? _startupUiRenderer; + private IHost? _startupServer; + private bool _disposed; + private bool _isUnhealthy; + + /// <summary> + /// Initializes a new instance of the <see cref="SetupServer"/> class. + /// </summary> + /// <param name="networkManagerFactory">The networkmanager.</param> + /// <param name="applicationPaths">The application paths.</param> + /// <param name="serverApplicationHostFactory">The servers application host.</param> + /// <param name="loggerFactory">The logger factory.</param> + /// <param name="startupConfiguration">The startup configuration.</param> + public SetupServer( + Func<INetworkManager?> networkManagerFactory, + IApplicationPaths applicationPaths, + Func<IServerApplicationHost?> serverApplicationHostFactory, + ILoggerFactory loggerFactory, + IConfiguration startupConfiguration) + { + _networkManagerFactory = networkManagerFactory; + _applicationPaths = applicationPaths; + _serverFactory = serverApplicationHostFactory; + _loggerFactory = loggerFactory; + _startupConfiguration = startupConfiguration; + var xmlSerializer = new MyXmlSerializer(); + _configurationManager = new ServerConfigurationManager(_applicationPaths, loggerFactory, xmlSerializer); + _configurationManager.RegisterConfiguration<NetworkConfigurationFactory>(); + } + + internal static ConcurrentQueue<StartupLogTopic>? LogQueue { get; set; } = new(); + + /// <summary> + /// Gets a value indicating whether Startup server is currently running. + /// </summary> + public bool IsAlive { get; internal set; } + + /// <summary> + /// Starts the Bind-All Setup aspcore server to provide a reflection on the current core setup. + /// </summary> + /// <returns>A Task.</returns> + public async Task RunAsync() + { + var fileTemplate = await File.ReadAllTextAsync(Path.Combine(AppContext.BaseDirectory, "ServerSetupApp", "index.mstemplate.html")).ConfigureAwait(false); + _startupUiRenderer = (await ParserOptionsBuilder.New() + .WithTemplate(fileTemplate) + .WithFormatter( + (StartupLogTopic logEntry, IEnumerable<StartupLogTopic> children) => + { + if (children.Any()) + { + var maxLevel = logEntry.LogLevel; + var stack = new Stack<StartupLogTopic>(children); + + while (maxLevel != LogLevel.Error && stack.Count > 0 && (logEntry = stack.Pop()) is not null) // error is the highest inherted error level. + { + maxLevel = maxLevel < logEntry.LogLevel ? logEntry.LogLevel : maxLevel; + foreach (var child in logEntry.Children) + { + stack.Push(child); + } + } + + return maxLevel; + } + + return logEntry.LogLevel; + }, + "FormatLogLevel") + .WithFormatter( + (LogLevel logLevel) => + { + switch (logLevel) + { + case LogLevel.Trace: + case LogLevel.Debug: + case LogLevel.None: + return "success"; + case LogLevel.Information: + return "info"; + case LogLevel.Warning: + return "warn"; + case LogLevel.Error: + return "danger"; + case LogLevel.Critical: + return "danger-strong"; + } + + return string.Empty; + }, + "ToString") + .BuildAndParseAsync() + .ConfigureAwait(false)) + .CreateCompiledRenderer(); + + ThrowIfDisposed(); + var retryAfterValue = TimeSpan.FromSeconds(5); + var config = _configurationManager.GetNetworkConfiguration()!; + _startupServer = Host.CreateDefaultBuilder(["hostBuilder:reloadConfigOnChange=false"]) + .UseConsoleLifetime() + .UseSerilog() + .ConfigureServices(serv => + { + serv.AddSingleton(this); + serv.AddHealthChecks() + .AddCheck<SetupHealthcheck>("StartupCheck"); + serv.Configure<ForwardedHeadersOptions>(options => + { + ApiServiceCollectionExtensions.ConfigureForwardHeaders(config, options); + }); + }) + .ConfigureWebHostDefaults(webHostBuilder => + { + webHostBuilder + .UseKestrel((builderContext, options) => + { + var knownBindInterfaces = NetworkManager.GetInterfacesCore(_loggerFactory.CreateLogger<SetupServer>(), config.EnableIPv4, config.EnableIPv6); + knownBindInterfaces = NetworkManager.FilterBindSettings(config, knownBindInterfaces.ToList(), config.EnableIPv4, config.EnableIPv6); + var bindInterfaces = NetworkManager.GetAllBindInterfaces(false, _configurationManager, knownBindInterfaces, config.EnableIPv4, config.EnableIPv6); + Extensions.WebHostBuilderExtensions.SetupJellyfinWebServer( + bindInterfaces, + config.InternalHttpPort, + null, + null, + _startupConfiguration, + _applicationPaths, + _loggerFactory.CreateLogger<SetupServer>(), + builderContext, + options); + }) + .Configure(app => + { + app.UseHealthChecks("/health"); + app.UseForwardedHeaders(); + app.Map("/startup/logger", loggerRoute => + { + loggerRoute.Run(async context => + { + var networkManager = _networkManagerFactory(); + if (context.Connection.RemoteIpAddress is null || networkManager is null || !networkManager.IsInLocalNetwork(context.Connection.RemoteIpAddress)) + { + context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + return; + } + + var logFilePath = new DirectoryInfo(_applicationPaths.LogDirectoryPath) + .EnumerateFiles() + .OrderByDescending(f => f.CreationTimeUtc) + .FirstOrDefault() + ?.FullName; + if (logFilePath is not null) + { + await context.Response.SendFileAsync(logFilePath, CancellationToken.None).ConfigureAwait(false); + } + }); + }); + + app.Map("/System/Info/Public", systemRoute => + { + systemRoute.Run(async context => + { + var jfApplicationHost = _serverFactory(); + + var retryCounter = 0; + while (jfApplicationHost is null && retryCounter < 5) + { + await Task.Delay(500).ConfigureAwait(false); + jfApplicationHost = _serverFactory(); + retryCounter++; + } + + if (jfApplicationHost is null) + { + context.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable; + context.Response.Headers.RetryAfter = new StringValues(retryAfterValue.TotalSeconds.ToString("000", CultureInfo.InvariantCulture)); + return; + } + + var sysInfo = new PublicSystemInfo + { + Version = jfApplicationHost.ApplicationVersionString, + ProductName = jfApplicationHost.Name, + Id = jfApplicationHost.SystemId, + ServerName = jfApplicationHost.FriendlyName, + LocalAddress = jfApplicationHost.GetSmartApiUrl(context.Request), + StartupWizardCompleted = false + }; + + await context.Response.WriteAsJsonAsync(sysInfo).ConfigureAwait(false); + }); + }); + + app.Run(async (context) => + { + context.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable; + context.Response.Headers.RetryAfter = new StringValues(retryAfterValue.TotalSeconds.ToString("000", CultureInfo.InvariantCulture)); + context.Response.Headers.ContentType = new StringValues("text/html"); + var networkManager = _networkManagerFactory(); + + var startupLogEntries = LogQueue?.ToArray() ?? []; + await _startupUiRenderer.RenderAsync( + new Dictionary<string, object>() + { + { "isInReportingMode", _isUnhealthy }, + { "retryValue", retryAfterValue }, + { "version", typeof(Emby.Server.Implementations.ApplicationHost).Assembly.GetName().Version! }, + { "logs", startupLogEntries }, + { "networkManagerReady", networkManager is not null }, + { "localNetworkRequest", networkManager is not null && context.Connection.RemoteIpAddress is not null && networkManager.IsInLocalNetwork(context.Connection.RemoteIpAddress) } + }, + new ByteCounterStream(context.Response.BodyWriter.AsStream(), IODefaults.FileStreamBufferSize, true, _startupUiRenderer.ParserOptions)) + .ConfigureAwait(false); + }); + }); + }) + .Build(); + await _startupServer.StartAsync().ConfigureAwait(false); + IsAlive = true; + } + + /// <summary> + /// Stops the Setup server. + /// </summary> + /// <returns>A task. Duh.</returns> + public async Task StopAsync() + { + ThrowIfDisposed(); + if (_startupServer is null) + { + throw new InvalidOperationException("Tried to stop a non existing startup server"); + } + + await _startupServer.StopAsync().ConfigureAwait(false); + IsAlive = false; + } + + /// <inheritdoc/> + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + _startupServer?.Dispose(); + IsAlive = false; + LogQueue?.Clear(); + LogQueue = null; + } + + private void ThrowIfDisposed() + { + ObjectDisposedException.ThrowIf(_disposed, this); + } + + internal void SoftStop() + { + _isUnhealthy = true; + } + + private class SetupHealthcheck : IHealthCheck + { + private readonly SetupServer _startupServer; + + public SetupHealthcheck(SetupServer startupServer) + { + _startupServer = startupServer; + } + + public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + if (_startupServer._isUnhealthy) + { + return Task.FromResult(HealthCheckResult.Unhealthy("Server is could not complete startup. Check logs.")); + } + + return Task.FromResult(HealthCheckResult.Degraded("Server is still starting up.")); + } + } + + internal sealed class SetupLoggerFactory : ILoggerProvider, IDisposable + { + private bool _disposed; + + public ILogger CreateLogger(string categoryName) + { + return new CatchingSetupServerLogger(); + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + } + } + + internal sealed class CatchingSetupServerLogger : ILogger + { + public IDisposable? BeginScope<TState>(TState state) + where TState : notnull + { + return null; + } + + public bool IsEnabled(LogLevel logLevel) + { + return logLevel is LogLevel.Error or LogLevel.Critical; + } + + public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) + { + if (!IsEnabled(logLevel)) + { + return; + } + + LogQueue?.Enqueue(new() + { + LogLevel = logLevel, + Content = formatter(state, exception), + DateOfCreation = DateTimeOffset.Now + }); + } + } +} diff --git a/Jellyfin.Server/ServerSetupApp/StartupLogTopic.cs b/Jellyfin.Server/ServerSetupApp/StartupLogTopic.cs new file mode 100644 index 00000000..cd440a9b --- /dev/null +++ b/Jellyfin.Server/ServerSetupApp/StartupLogTopic.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.ObjectModel; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.ServerSetupApp; + +/// <summary> +/// Defines a topic for the Startup UI. +/// </summary> +public class StartupLogTopic +{ + /// <summary> + /// Gets or Sets the LogLevel. + /// </summary> + public LogLevel LogLevel { get; set; } + + /// <summary> + /// Gets or Sets the descriptor for the topic. + /// </summary> + public string? Content { get; set; } + + /// <summary> + /// Gets or sets the time the topic was created. + /// </summary> + public DateTimeOffset DateOfCreation { get; set; } + + /// <summary> + /// Gets the child items of this topic. + /// </summary> + public Collection<StartupLogTopic> Children { get; } = []; +} diff --git a/Jellyfin.Server/ServerSetupApp/StartupLogger.cs b/Jellyfin.Server/ServerSetupApp/StartupLogger.cs new file mode 100644 index 00000000..0121854c --- /dev/null +++ b/Jellyfin.Server/ServerSetupApp/StartupLogger.cs @@ -0,0 +1,124 @@ +using System; +using System.Globalization; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Jellyfin.Server.ServerSetupApp; + +/// <inheritdoc/> +public class StartupLogger : IStartupLogger +{ + private readonly StartupLogTopic? _topic; + + /// <summary> + /// Initializes a new instance of the <see cref="StartupLogger"/> class. + /// </summary> + /// <param name="logger">The underlying base logger.</param> + public StartupLogger(ILogger logger) + { + BaseLogger = logger; + } + + /// <summary> + /// Initializes a new instance of the <see cref="StartupLogger"/> class. + /// </summary> + /// <param name="logger">The underlying base logger.</param> + /// <param name="topic">The group for this logger.</param> + internal StartupLogger(ILogger logger, StartupLogTopic? topic) : this(logger) + { + _topic = topic; + } + + internal static IStartupLogger Logger { get; set; } = new StartupLogger(NullLogger.Instance); + + /// <inheritdoc/> + public StartupLogTopic? Topic => _topic; + + /// <summary> + /// Gets or Sets the underlying base logger. + /// </summary> + protected ILogger BaseLogger { get; set; } + + /// <inheritdoc/> + public IStartupLogger BeginGroup(FormattableString logEntry) + { + return new StartupLogger(BaseLogger, AddToTopic(logEntry)); + } + + /// <inheritdoc/> + public IStartupLogger With(ILogger logger) + { + return new StartupLogger(logger, Topic); + } + + /// <inheritdoc/> + public IStartupLogger<TCategory> With<TCategory>(ILogger logger) + { + return new StartupLogger<TCategory>(logger, Topic); + } + + /// <inheritdoc/> + public IStartupLogger<TCategory> BeginGroup<TCategory>(FormattableString logEntry) + { + return new StartupLogger<TCategory>(BaseLogger, AddToTopic(logEntry)); + } + + private StartupLogTopic AddToTopic(FormattableString logEntry) + { + var startupEntry = new StartupLogTopic() + { + Content = logEntry.ToString(CultureInfo.InvariantCulture), + DateOfCreation = DateTimeOffset.Now + }; + + if (Topic is null) + { + SetupServer.LogQueue?.Enqueue(startupEntry); + } + else + { + Topic.Children.Add(startupEntry); + } + + return startupEntry; + } + + /// <inheritdoc/> + public IDisposable? BeginScope<TState>(TState state) + where TState : notnull + { + return null; + } + + /// <inheritdoc/> + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + /// <inheritdoc/> + public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) + { + if (BaseLogger.IsEnabled(logLevel)) + { + // if enabled allow the base logger also to receive the message + BaseLogger.Log(logLevel, eventId, state, exception, formatter); + } + + var startupEntry = new StartupLogTopic() + { + LogLevel = logLevel, + Content = formatter(state, exception), + DateOfCreation = DateTimeOffset.Now + }; + + if (Topic is null) + { + SetupServer.LogQueue?.Enqueue(startupEntry); + } + else + { + Topic.Children.Add(startupEntry); + } + } +} diff --git a/Jellyfin.Server/ServerSetupApp/StartupLoggerExtensions.cs b/Jellyfin.Server/ServerSetupApp/StartupLoggerExtensions.cs new file mode 100644 index 00000000..ada4b56a --- /dev/null +++ b/Jellyfin.Server/ServerSetupApp/StartupLoggerExtensions.cs @@ -0,0 +1,18 @@ +using System; +using System.Globalization; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Jellyfin.Server.ServerSetupApp; + +internal static class StartupLoggerExtensions +{ + public static IServiceCollection RegisterStartupLogger(this IServiceCollection services) + { + return services + .AddTransient<IStartupLogger, StartupLogger<Startup>>() + .AddTransient(typeof(IStartupLogger<>), typeof(StartupLogger<>)); + } +} diff --git a/Jellyfin.Server/ServerSetupApp/StartupLoggerOfCategory.cs b/Jellyfin.Server/ServerSetupApp/StartupLoggerOfCategory.cs new file mode 100644 index 00000000..64da0ce8 --- /dev/null +++ b/Jellyfin.Server/ServerSetupApp/StartupLoggerOfCategory.cs @@ -0,0 +1,56 @@ +using System; +using System.Globalization; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.ServerSetupApp; + +/// <summary> +/// Startup logger for usage with DI that utilises an underlying logger from the DI. +/// </summary> +/// <typeparam name="TCategory">The category of the underlying logger.</typeparam> +#pragma warning disable SA1649 // File name should match first type name +public class StartupLogger<TCategory> : StartupLogger, IStartupLogger<TCategory> +#pragma warning restore SA1649 // File name should match first type name +{ + /// <summary> + /// Initializes a new instance of the <see cref="StartupLogger{TCategory}"/> class. + /// </summary> + /// <param name="logger">The injected base logger.</param> + public StartupLogger(ILogger<TCategory> logger) : base(logger) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="StartupLogger{TCategory}"/> class. + /// </summary> + /// <param name="logger">The underlying base logger.</param> + /// <param name="groupEntry">The group for this logger.</param> + internal StartupLogger(ILogger logger, StartupLogTopic? groupEntry) : base(logger, groupEntry) + { + } + + IStartupLogger<TCategory> IStartupLogger<TCategory>.BeginGroup(FormattableString logEntry) + { + var startupEntry = new StartupLogTopic() + { + Content = logEntry.ToString(CultureInfo.InvariantCulture), + DateOfCreation = DateTimeOffset.Now + }; + + if (Topic is null) + { + SetupServer.LogQueue?.Enqueue(startupEntry); + } + else + { + Topic.Children.Add(startupEntry); + } + + return new StartupLogger<TCategory>(BaseLogger, startupEntry); + } + + IStartupLogger<TCategory> IStartupLogger<TCategory>.With(ILogger logger) + { + return new StartupLogger<TCategory>(logger, Topic); + } +} diff --git a/Jellyfin.Server/ServerSetupApp/index.mstemplate.html b/Jellyfin.Server/ServerSetupApp/index.mstemplate.html new file mode 100644 index 00000000..890a7761 --- /dev/null +++ b/Jellyfin.Server/ServerSetupApp/index.mstemplate.html @@ -0,0 +1,235 @@ +<!DOCTYPE html> +<html> + +<head> + <meta charset="UTF-8" /> + <title> + {{#IF isInReportingMode}} + ❌ + {{/IF}} + Jellyfin Startup + + + + + +
+
+ + {{^IF isInReportingMode}} +

Jellyfin Server {{version}} still starting. Please wait.

+ {{#ELSE}} +

Jellyfin Server has encountered an error and was not able to start.

+ {{/ELSE}} + {{/IF}} + + {{#IF localNetworkRequest}} +

You can download the current log file here.

+ {{/IF}} +
+ + {{#DECLARE LogEntry |--}} + {{#LET children = Children}} +
  • + {{--| #IF children.Count > 0}} +
    + {{DateOfCreation}} - {{Content}} +
      + {{--| #EACH children.Reverse() |-}} + {{#IMPORT 'LogEntry'}} + {{--| /EACH |-}} +
    +
    + {{--| #ELSE |-}} + {{DateOfCreation}} - {{Content}} + {{--| /ELSE |--}} + {{--| /IF |-}} +
  • + {{--| /DECLARE}} + + {{#IF localNetworkRequest}} +
    +
      + {{#FOREACH log IN logs.Reverse()}} + {{#IMPORT 'LogEntry' #WITH log}} + {{/FOREACH}} +
    +
    + {{#ELSE}} + {{#IF networkManagerReady}} +

    Please visit this page from your local network to view detailed startup logs.

    + {{#ELSE}} +

    Initializing network settings. Please wait.

    + {{/ELSE}} + {{/IF}} + {{/ELSE}} + {{/IF}} +
    + + +{{^IF isInReportingMode}} + +{{/IF}} + + diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs new file mode 100644 index 00000000..f6a4ae7d --- /dev/null +++ b/Jellyfin.Server/Startup.cs @@ -0,0 +1,235 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Mime; +using System.Text; +using Emby.Server.Implementations.EntryPoints; +using Jellyfin.Api.Middleware; +using Jellyfin.Database.Implementations; +using Jellyfin.LiveTv.Extensions; +using Jellyfin.LiveTv.Recordings; +using Jellyfin.MediaEncoding.Hls.Extensions; +using Jellyfin.Networking; +using Jellyfin.Networking.HappyEyeballs; +using Jellyfin.Server.Extensions; +using Jellyfin.Server.HealthChecks; +using Jellyfin.Server.Implementations.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Extensions; +using MediaBrowser.XbmcMetadata; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Primitives; +using Prometheus; + +namespace Jellyfin.Server +{ + /// + /// Startup configuration for the Kestrel webhost. + /// + public class Startup + { + private readonly CoreAppHost _serverApplicationHost; + private readonly IConfiguration _configuration; + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// + /// Initializes a new instance of the class. + /// + /// The server application host. + /// The used Configuration. + public Startup(CoreAppHost appHost, IConfiguration configuration) + { + _serverApplicationHost = appHost; + _configuration = configuration; + _serverConfigurationManager = appHost.ConfigurationManager; + } + + /// + /// Configures the service collection for the webhost. + /// + /// The service collection. + public void ConfigureServices(IServiceCollection services) + { + services.AddResponseCompression(); + services.AddHttpContextAccessor(); + services.AddHttpsRedirection(options => + { + options.HttpsPort = _serverApplicationHost.HttpsPort; + }); + + services.AddJellyfinApi(_serverApplicationHost.GetApiPluginAssemblies(), _serverConfigurationManager.GetNetworkConfiguration()); + services.AddJellyfinDbContext(_serverApplicationHost.ConfigurationManager, _configuration); + services.AddJellyfinApiSwagger(); + + // configure custom legacy authentication + services.AddCustomAuthentication(); + + services.AddJellyfinApiAuthorization(); + + var productHeader = new ProductInfoHeaderValue( + _serverApplicationHost.Name.Replace(' ', '-'), + _serverApplicationHost.ApplicationVersionString); + var acceptJsonHeader = new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json, 1.0); + var acceptXmlHeader = new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Xml, 0.9); + var acceptAnyHeader = new MediaTypeWithQualityHeaderValue("*/*", 0.8); + Func eyeballsHttpClientHandlerDelegate = (_) => new SocketsHttpHandler() + { + AutomaticDecompression = DecompressionMethods.All, + RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8, + ConnectCallback = HttpClientExtension.OnConnect + }; + + Func defaultHttpClientHandlerDelegate = (_) => new SocketsHttpHandler() + { + AutomaticDecompression = DecompressionMethods.All, + RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8 + }; + + services.AddHttpClient(NamedClient.Default, c => + { + c.DefaultRequestHeaders.UserAgent.Add(productHeader); + c.DefaultRequestHeaders.Accept.Add(acceptJsonHeader); + c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader); + c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader); + }) + .ConfigurePrimaryHttpMessageHandler(eyeballsHttpClientHandlerDelegate); + + services.AddHttpClient(NamedClient.MusicBrainz, c => + { + c.DefaultRequestHeaders.UserAgent.Add(productHeader); + c.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue($"({_serverApplicationHost.ApplicationUserAgentAddress})")); + c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader); + c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader); + }) + .ConfigurePrimaryHttpMessageHandler(eyeballsHttpClientHandlerDelegate); + + services.AddHttpClient(NamedClient.DirectIp, c => + { + c.DefaultRequestHeaders.UserAgent.Add(productHeader); + c.DefaultRequestHeaders.Accept.Add(acceptJsonHeader); + c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader); + c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader); + }) + .ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate); + + services.AddHealthChecks() + .AddCheck>(nameof(JellyfinDbContext)); + + services.AddHlsPlaylistGenerator(); + services.AddLiveTvServices(); + + services.AddHostedService(); + services.AddHostedService(); + services.AddHostedService(); + services.AddHostedService(); + services.AddHostedService(); + services.AddHostedService(); + } + + /// + /// Configures the app builder for the webhost. + /// + /// The application builder. + /// The webhost environment. + /// The application config. + public void Configure( + IApplicationBuilder app, + IWebHostEnvironment env, + IConfiguration appConfig) + { + app.UseBaseUrlRedirection(); + + // Wrap rest of configuration so everything only listens on BaseUrl. + var config = _serverConfigurationManager.GetNetworkConfiguration(); + app.Map(config.BaseUrl, mainApp => + { + if (env.IsDevelopment()) + { + mainApp.UseDeveloperExceptionPage(); + } + + mainApp.UseForwardedHeaders(); + mainApp.UseMiddleware(); + + mainApp.UseMiddleware(); + + mainApp.UseWebSockets(); + + mainApp.UseResponseCompression(); + + mainApp.UseCors(); + + if (config.RequireHttps && _serverApplicationHost.ListenWithHttps) + { + mainApp.UseHttpsRedirection(); + } + + if (appConfig.HostWebClient()) + { + var extensionProvider = new FileExtensionContentTypeProvider(); + + // subtitles octopus requires .data, .mem files. + extensionProvider.Mappings.Add(".data", MediaTypeNames.Application.Octet); + extensionProvider.Mappings.Add(".mem", MediaTypeNames.Application.Octet); + mainApp.UseDefaultFiles(new DefaultFilesOptions + { + FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath), + RequestPath = "/web" + }); + mainApp.UseStaticFiles(new StaticFileOptions + { + FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath), + RequestPath = "/web", + ContentTypeProvider = extensionProvider, + OnPrepareResponse = (context) => + { + if (Path.GetFileName(context.File.Name).Equals("index.html", StringComparison.Ordinal)) + { + context.Context.Response.Headers.CacheControl = new StringValues("no-cache"); + } + } + }); + + mainApp.UseRobotsRedirection(); + } + + mainApp.UseStaticFiles(); + mainApp.UseAuthentication(); + mainApp.UseJellyfinApiSwagger(_serverConfigurationManager); + mainApp.UseQueryStringDecoding(); + mainApp.UseRouting(); + mainApp.UseAuthorization(); + + mainApp.UseIPBasedAccessValidation(); + mainApp.UseWebSocketHandler(); + mainApp.UseServerStartupMessage(); + + if (_serverConfigurationManager.Configuration.EnableMetrics) + { + // Must be registered after any middleware that could change HTTP response codes or the data will be bad + mainApp.UseHttpMetrics(); + } + + mainApp.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + if (_serverConfigurationManager.Configuration.EnableMetrics) + { + endpoints.MapMetrics(); + } + + endpoints.MapHealthChecks("/health"); + }); + }); + } + } +} diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs new file mode 100644 index 00000000..4890ccbb --- /dev/null +++ b/Jellyfin.Server/StartupOptions.cs @@ -0,0 +1,113 @@ +using System.Collections.Generic; +using CommandLine; +using Emby.Server.Implementations; +using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; + +namespace Jellyfin.Server +{ + /// + /// Class used by CommandLine package when parsing the command line arguments. + /// + public class StartupOptions : IStartupOptions + { + /// + /// Gets or sets the path to the data directory. + /// + /// The path to the data directory. + [Option('d', "datadir", Required = false, HelpText = "Path to use for the data folder (database files, etc.).")] + public string? DataDir { get; set; } + + /// + /// Gets or sets a value indicating whether the server should not host the web client. + /// + [Option("nowebclient", Required = false, HelpText = "Indicates that the web server should not host the web client.")] + public bool NoWebClient { get; set; } + + /// + /// Gets or sets the path to the web directory. + /// + /// The path to the web directory. + [Option('w', "webdir", Required = false, HelpText = "Path to the Jellyfin web UI resources.")] + public string? WebDir { get; set; } + + /// + /// Gets or sets the path to the cache directory. + /// + /// The path to the cache directory. + [Option('C', "cachedir", Required = false, HelpText = "Path to use for caching.")] + public string? CacheDir { get; set; } + + /// + /// Gets or sets the path to the config directory. + /// + /// The path to the config directory. + [Option('c', "configdir", Required = false, HelpText = "Path to use for configuration data (user settings and pictures).")] + public string? ConfigDir { get; set; } + + /// + /// Gets or sets the path to the log directory. + /// + /// The path to the log directory. + [Option('l', "logdir", Required = false, HelpText = "Path to use for writing log files.")] + public string? LogDir { get; set; } + + /// + [Option("ffmpeg", Required = false, HelpText = "Path to external FFmpeg executable to use in place of default found in PATH.")] + public string? FFmpegPath { get; set; } + + /// + [Option("service", Required = false, HelpText = "Run as headless service.")] + public bool IsService { get; set; } + + /// + [Option("package-name", Required = false, HelpText = "Used when packaging Jellyfin (example, synology).")] + public string? PackageName { get; set; } + + /// + [Option("published-server-url", Required = false, HelpText = "Jellyfin Server URL to publish via auto discover process")] + public string? PublishedServerUrl { get; set; } + + /// + /// Gets or sets a value indicating whether the server should not detect network status change. + /// + [Option("nonetchange", Required = false, HelpText = "Indicates that the server should not detect network status change.")] + public bool NoDetectNetworkChange { get; set; } + + /// + /// Gets or sets the path to an jellyfin backup archive to restore the application to. + /// + [Option("restore-archive", Required = false, HelpText = "Path to a Jellyfin backup archive to restore from")] + public string? RestoreArchive { get; set; } + + /// + /// Gets the command line options as a dictionary that can be used in the .NET configuration system. + /// + /// The configuration dictionary. + public Dictionary ConvertToConfig() + { + var config = new Dictionary(); + + if (NoWebClient) + { + config.Add(HostWebClientKey, bool.FalseString); + } + + if (PublishedServerUrl is not null) + { + config.Add(AddressOverrideKey, PublishedServerUrl); + } + + if (FFmpegPath is not null) + { + config.Add(FfmpegPathKey, FFmpegPath); + } + + if (NoDetectNetworkChange) + { + config.Add(DetectNetworkChangeKey, bool.FalseString); + } + + return config; + } + } +} diff --git a/Jellyfin.Server/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs b/Jellyfin.Server/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs new file mode 100644 index 00000000..925b1351 --- /dev/null +++ b/Jellyfin.Server/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")] diff --git a/Jellyfin.Server/obj/Debug/net10.0/Jellyfin.Server.GeneratedMSBuildEditorConfig.editorconfig b/Jellyfin.Server/obj/Debug/net10.0/Jellyfin.Server.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 00000000..db792fc1 --- /dev/null +++ b/Jellyfin.Server/obj/Debug/net10.0/Jellyfin.Server.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,41 @@ +is_global = true +build_property.TargetFramework = net10.0 +build_property.TargetFramework = net10.0 +build_property.TargetFrameworkIdentifier = .NETCoreApp +build_property.TargetFrameworkIdentifier = .NETCoreApp +build_property.TargetFrameworkVersion = v10.0 +build_property.TargetFrameworkVersion = v10.0 +build_property.TargetPlatformMinVersion = +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = true +build_property.UsingMicrosoftNETSdkWeb = true +build_property.ProjectTypeGuids = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = Jellyfin.Server +build_property.RootNamespace = Jellyfin.Server +build_property.ProjectDir = /srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server/ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.RazorLangVersion = 9.0 +build_property.SupportLocalizedComponentNames = +build_property.GenerateRazorMetadataSourceChecksumAttributes = +build_property.MSBuildProjectDirectory = /srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server +build_property._RazorSourceGeneratorDebug = +build_property.EffectiveAnalysisLevelStyle = 10.0 +build_property.EnableCodeStyleSeverity = + +[/srv/common_drive/Projects/pgsql-jellyfin/BannedSymbols.txt] +build_metadata.AdditionalFiles.TargetPath = +build_metadata.AdditionalFiles.CssScope = + +[/srv/common_drive/Projects/pgsql-jellyfin/stylecop.json] +build_metadata.AdditionalFiles.TargetPath = +build_metadata.AdditionalFiles.CssScope = diff --git a/Jellyfin.Server/obj/Debug/net10.0/Jellyfin.Server.assets.cache b/Jellyfin.Server/obj/Debug/net10.0/Jellyfin.Server.assets.cache new file mode 100644 index 0000000000000000000000000000000000000000..dceaf505c96067a31e44623883ad9a47a0661e75 GIT binary patch literal 56091 zcmdUY2Y4LEm8P^3Ns$yukrXMCA|+Beu?GWyASqjxL4ZjDDT)9jIZBP01~B9#O%Diy zmTXzJY)f*Klbo}?&e@i;ENAc9>-Da8_tIY8i+f?i-R3(q;{oJQZ^ZGYbT%;qQdeEG&-|H)Ht`SnwO_nT$c?mzMR z)em0v7n?76{4YO!&N&xAu9%yyRh-=HiCWchb5?a`%4z26cHyKwh2N!O$*tF1d!p>v zRl7WQ#%Z{@eyhjYlM8+I@gF?DHR$u zw>H_d3bmpS?v`9@!md`GqFpbA@cmXEj1u`A36u;$iARY__IXMM>!n1J=PQy+aDB(K zM{;O%+HTZ4;QDx0u1>XCYR*kI?20p6Yn&|98jcm3Gv9;9+t~Rs5fn)#@=Nv(}!^p&T=c9X2kMLW7>A7UDhcmL8vfy*745fH70Yp4?>3^IGKb8|D~q zQ?+O}ig=lFL%!$;M5Ud#Da^}oWxg**IB3Kymh3{K)GVPbM6(n=QkKr);GV?T4xP-` z4g_LcfpBLV47V`TER|ifj17nNBbcp- z4uC={xwX9m13hNI!@iKr;WfaJnAdiI={8XxS6nmZs4pZjuZv+`kKg9HCCukcang)8 zUYwLzH^i`R#P4I~C+KyUtf_KsBGmXz#*i8y-C$4;e z7Q!t?Fz0l^WoZPEhT+S;|Mbx4sMMTOntPPyJg#Pw;fb+8_RlSBJ zc=qg_=Z-*TpD)9ft5%hehZW>)3i5Vb@9PZGI^hC$H~X17rzyC%K~RE^C~)@WSO|}H z0$!PMO9emgUf(V?(?=+%3?Le}I~4da1%4;41#`}W@nO+!+Ln7iUw6M*Rl^|h%7uf; zVG57>!UF%e!sl3rnIA={WyDVylkwHzNK8@ipFlu3X#^&?jK#Q&x}7r{6scu-xhqEVZv1{^J~SIn*(OK3+&RhPL0U=io*2a!;P=Dxp_pFE zGCbneQuo3bzZc>6gIP=6>84Y4`ADszQ+KLGr&^e+V7S=MdSSZZAl(tBla_mNjM+=@ z`|Fu47eb&@jn>X(uiBb+>~eFuFzpmhx>iCtZS&MarRa?ZN)LHThkT`NUaXhKGIcM0 zzciz{YcMffR~UZ{SiK4D6>9e2{k{XT@K8y6-lw2#Tv@+NAe_tyJzJ{w^;+&6Mx7Ps z6b$F)FeZr(pF{AwJ?T+TdcP+vc@z|nqT=D;dc@2FeHgTr80*$@7@(jNHvpfCs@T}; zI1&m-q)CM|rI4m^9Wf$RorwwxFT07>E{za2(>=MP`yl|A2nN5Rx-2Q66AI`guI6q* z)tPPLy`h8BYc2Ea{YHlHM?;BtGg($}6$Mwt^>$;v>ScSbTymQ(J*^zS2j*k%>X^QJ zbpM#lR!!m570&&*nnw%u8hUSNeFsA8@%bEMwA9>Cnq8&2iK}^N>}mGf)`)tKzo$O_ zP-bEnr|{c6SgkkU%Wh6PGwx2>X%BiZft_|U8^L`GgR4>H0g(BcRhXx7kSacsatL(F)3Y>y0ymP9wb@%2zA3zd~t$2v=%5tye?=XcV>bn)He&+ANZ*x1Q%bjoK z$r3V)<|{O^tXO@}1CWuJ?^T%Z!<9CEKf*)@HlAvrzfogeg^k&S>Z;C%J?M+I^rt?c z;6I2f;Xj1X$s_I%Ui9SR$R7ssQFF$pOOpV*D;bn-Z%hTP*FLf*BN6am4O0fCG_WF(4WWe4lIEV*Qrtw6Mbp6 zV6QAG3S*xHJh+%_Yv~hFDyD34wq(F4J{xW`>-LNk7 z_-=z_j273xS7H!f#qZ&^`QYo5k_(n+nTM~%0KSgj=AN`Wi#f`PnZij_q+uuCYB1V8Y0=+2?hMxolE+hu$2V|g(fuZZ*(aF71JP4cn8VzSX_mls;I#wEOh$cq z$>m#$%eNJm@8CLPHoDL)Y@)bKIdl?tMCYIraTk9##^`(a?aqVIOsP{srng9Ed%I%MF|epXN_vC_lq*bK5ynbS$*SItOsHh$#>q%cyY(V*Eau;6eDg zLj65lnYZ6ZFjvtt%}K28Ia#gEaz2hN0&}W*1>?=hasQso+AkEyFBQli;A*Zv&-C~A zSa!4Qdti2>Eevr8IXE^V0e+e{gDd}N#|2;DQZG)hkHf!B2>gFDf_X$ZAs5ngzpzDMUn;N{biQ3+ z^eT)#T+JP)a1y8+XSIO5PeIaIy@1YPfgqj3^KfbkjSa=w#ns%A&*;n)x}`JI+!2X6 z>E`3o6Zq1lXU<#eAVpM1;kZHQmV*>?B@yWEbZ{cjqnG-NSNZSSeengRj9nF30?L?6A~9C*`t#mec4M)KlFKIjr7w(K5w3IO z&(+bzh>ThrMych3u~@aBkFl#lSuDF_%u2B`j=z_9E7|zpD(%Yf`kRc|9*MDz!ex5O z8Fb-FbuF--Io(dEVi)chZ71Z-mQrYmxAu(xUF@$yi>?IoZy-ZzM@6tgn>UvFE4uja zd02zR>+0xADMl?pRqQ(aZ+WoZh`-jhED2)l#+F!%Gm-IIVmS?OT_2XNHoGP$y&;qq z%VKzEOX3}G~pWLBT;mR|s~n-ho=yhFa88 z1jcR+V_Vk;AS-h4a%TN9fPXWWHy;+hdlyoqN1x5E1`C@rg_y&_6%jz)kiyBnG7q`)%e$>#s-v{yTckUbe}V3d883p7yPnxWlf>2VRUk>ohA^}*l7YmZGCm7{W~PHE#je#d$%dEHX=bM zGGn(yVmUqz+`znjdE_?AYs^(lnRTNr`*|3BX=}7Kt~Lnq{)*ASv&)~$`O|a4^4A6Y zMf>u{MmP!szh|`gZ%w{sc}`BIy(~ol(ezbPWQSP<{y<7?H%pBlrqBj|J(3l5qVdj3 z;b!9RWy%@E|JTG^HH=$1K6U`+@;om5y(;1yVa!JDorrofcxxTPB`rN4IBu}FWC`y5 z3BopnKSj6<;m;8M0m45-_(ur;7~!8FY)RTI=`AhTqt%=XF{(KaAxX`jBfclBk=wM^ z(9MmKTKV}(Ie#Z9|EGvQ6e<@J{GBYFQ|s|G=t~vovJ}vNhWHJl2?~ zNwoht;xCTY{r*B6tzcFuIGv*4Um*VSr~=WnM0Ih!4#}kH`j@!3S=YPa%cUmmHKT`> zmRI4Ll%;=#_@LsCH%0zp6s_tJrRp^)s{S?N148@5hRa`9qP1|*2($HQik81X{M9;J z$t_VVi%%$xX<7U?xF7Hm4VO1_7!+A993U?)97rYLcDN}3bt(@!&>ai6{@yZm%e~I`Vtxb{dH{UsdZ3^6{s%icwrR_hb zX!|dSXO*|jI1>PYbM_#ZeWl*Yl?HsZSA-fe-i zN#UF|@3z2}-fi=mmxAw1=ybcLz+$V*C&yOT?Bc|V@+{jlh3@T2_YPdm&YohhVUpcI z^G-r<7EYjCsZg%M)oiU0rK4?`0_&PCusYg~DX^{+SmyUO%vYvum;&PlfnnaE*6ChC z0kTVh4B&b;_YevStovBXNnx+CrcB-6C$!z7v^@`3v(-A?*C#-puRsQIHLt2Kcf8q7 zz#LF82XS4DO?Co`0}?)j+YlCabDaPkQJ_aU0qts2odCT|N=4Go8Ra zu5dY0Fwe4txY*Vb|FDS~f9MHlZ`G3+)FgbyL~s}r9DZ9i&k1^W33|T`o8|%7YU8qfRg3*fwqrab<<3wgSyl1t@mv%AN83@Sx6r_!-c{VtF zqOY9GtY9j*&SSlX&`1v> zi`#;&*ATkpdJS_|JnAco?)oxpIuls4Dj%nDUF6MX0+r4yT92saHz%6Id2KQi_zx)j z2XUR}<}#u66-w(vxVBk~;(tX^CXwH{*JiH;D-r^xf(*o~^98p6k_f!TAnc{YJHJ((IACLK2U^L%g|% zvp}041UET7L~dJCxcyJWn_J5+r;h{|IekPfm#1*~Ux;6j=_P@^9Xk1pIK4#dD-e>N z(f>yL0#82)7IONDEUr#r@qZ9+ej4F4PDj&Ig3C3)phY+pMlRPPBw6JDB0ejZj;5~! z7dd@JF4w1U`G1Hv*QKFdGEHv@4sv>n9BxeEz!`M&M2^3qq@%Bo>dN&dfbya2L?8(Z zA?cx<3le4*PrI*@YQ!86h}eTb#5_V0;_m>_Z2IYrzgDU_^a7I{`Vh#WA0dgud2LPK z>CB^e{<{E55}fuUiQNcEB+dtkvpF*>>8vNm5&)z%&Y6rqO^2)!R633@r8&7S1WCwhei4gi)k4kD1oA%r9vD?np0CV~YO zIT1`Mx2I592`Y;_Q7lNwiDHsEnnLQrPDph%kt|5biDZ(xGlkSDkTMSv!-<@(Cz=JX zUwu_=vFZ@KfVEp13lndA&J2yV6bo#^@4z$s3(DwDFilwfVunC`9!{; zAt&-lqnbivGicmytXn$!s;%yY)_}+7P)8vC{Rm0VVGHohqfqZN4w$h|``WEWYyg3X zE&>so2uX;S0@3Um%J|_3VeJ`UN$gW8*js^Zc7#Ny@iQ+}5L{*zm(#e?4s$77wt>qc zFJus`&cs-~EQQr&U}dhkq86IhLI=U`0mbh@TxqqJr|`QR{N}k3LZH4vp+1BwQD2FW zRA#pWHQTgjI5C)Up@d-bFwj|wIf+j;uSQ5>vjc3*y?;F|I*)}Ef|p!KL0+#-;dMo3 zD|NikLa=!q&}k(uv>=0x>^IXUwxXFbanZr6a@SzhQNSUv$(@*RC^3d?K3GFy$&@j?*6 z=5312+i|7M-jTxQIv_6zV>n70ZqzHBXb>cKe zA5LMk6O0!4tHqkaM*zvU{G$kbuRey5)W%q#VD8Jh`b)#+4-AWP@^N4h`x6Mn&H*C{ zy9e=YY8Z{(>4(Gwn@`5rdcVI0RqJ~tfyK7_XfF2kpX<6l445A4&5=4WuF z(LS3(vlleYzJdr%Z+BvcUq#lGJ{P0(`4mchommdu@7O0WVz$fpwPa263o)8sOrhBi zn&uTe{J?R>uPAHqFDY>So_#q5d>6pY(=-In^mSzo{goK>S5u$|0Bv4eL}z(+DLkJ8o^84T9W76cEr=A1o>7dn zFY^Z}jGhZdZAQZ#F-lx8Y2fw46kfN0mw6ddH(!U=waAZ@b$*O1-_oC?uy`I=EYPw} zVS}H>(0`VKek;(;eU$lpO)KP zZx|fd!`_c8%gX^=*{3~->tEygHwb^L;twJ2Fv4vJwtS=XZBBLd7+wToNf}@~Gw@;rX5b|Vi}(th?uz_NK`Mzn<4OKr1d_iGVRlih-@V~& za7yCNcygaWAol{o;z%uv{h5!lf;|x<6PUx<|34pU}z%B~A1}E`5i8SL$x{N^56@+HD zq`eg&t)vmnmdWs9qDQWYS3y7$uiDSa&;v5w$R;)M^o#f z5?>^A8l*`6uMlMSm+>S&hd}aY5ZXAT0*7-#mt7yQ7tl}YvWH(Z4!i3w11^iUc8EWK z-$}-1Jo(D*y14=jobyRTjE?zw2MFQ-N^R#j?*~(OGd|9{VysKL?W??ScIe$g&eSezr>r&oi7 ztg0VLA zK_aPt#CVdG{i8O8q6@N-PDU2GZ%U!dc+!=fjD@G`l*Pa>3*9%T&}BU7z6F6r;R%E` z?OdnUt(59+$L4)2cqEku#*^>c5XkrK2yJ?u9rJDVt!AgF2aH}ACh-kxiGT+B-kXeW;|Jc3W2OYjj&){A&f)2wen}c zBZ)8L$@jAe-FGM-fB@P0vUDoQ{mqF+lP%6JlW z5olA+gDfo3u+Ggy^y?syWKqVG=qUseokduK5%mts%I&XA%vr~#YfVhmiT|yFi^a_+ zShV=}6jJd2Z{T_*QUwRS;`GnRk65~IigbCTJL2FZn|;0$07qq3Dz$1-TujTil$LMf zy0=wJwb`hZ%T6P%Nyo>ueMe}!TIPkv!G@Xf4u_3e;kzQGgOqNg5S5>>U8gn1I;NJ1 zyXr*M3?ebk-&4r6|E@T4c+y>{IE;&FdRpY+j<_c8@$9b_YQ<7@%DUZ|n{3z>#|_n{ z2*fbHuP~m$H3y8yQ}<4FOo)o<_<`UVl+&Yj1;qSv>i-{0^+S=W#ZE_^=4`ETk{W{Y z8^*;n{YYt|#cz%0VWd{D%RH-?-*buPC2}jK^T#Hg5z|I>MsCG){zT~99p@Poyr{k) zI;QKVLf0*EUH42>3dvcPcVZfUCN#1h`8j^CK00&IX&$o+C+#WcaPi`#V6YcDqr81#{v=QxX=ZxTBze?`D;TUghwFcZ$YNcwdGmJYePf zw6SqcAriQS@n)&)dgnQNmJFlR8SvVMQF(PUmP8#8pPWS9WRmFV@!Hf;1RN_*QF*NV z5=^>SeLGZ+xlzil*RfS9Cyt(H8vp2d*az|#i$~&}NbliCmgz3z%3fGU^WY33U zN{`ZmmwKI2%vS@Eycs*Gp6@k{AzI_2`*WT|T1Qz?&~@-hlFu7g?yIqhxDUY^T8 zT!)h3QTHo)dwO?S1HCY#e)ge3^0|>l;h5dPga}iuW$n7JVVA0BRM*HW$a(!({iC4b zGEBL#C8c$niwbi$s~Y|4r6i|dT*r|aw^Z<%c(qbKO@|PqjxFJ;7q9o^A2jd1LRYD0 zz-N>!h4kCPI|;|p?^G)Mj0Edz5+un*9#)@lXx88azO8B(cx1hMb9Tit8Bwk?GRDEv zWh|GkRqgVg>=Ct+~xo!L<%KcDXq{H0=~lx>iEw?o7#$fbBpVIjs#j-GLlp@PZb5!0PS2PCC^oEzgaGlE9Nj-rDKBF` z1Dyz6HAvc?K+tThIqJ+dQO0YNO|MQ^nrDsPf0;mtNf712V`UpFG2NznbpO~AHvaib zRSLeq@~%hf7{*_7s4DPTBHgTzyBAG?75;xbJybW#o*pI(sIJC6gBZ#9q4px&rBsDzPy{ZOgLBu#b8M*Z(M zc-7KOIX!+4v`2S#yn3!xiiv}>p5upW0}>71wc=T61#wBLSs&aC z57u77QW$XHQ%VqG;w@o()2Xt9jyIZbc6I6og&)a*7?CQmG%X{wsi_QFlvqT|fm)+t zH_3rbXdM+ix<<&)^$8Yfm8GiS-16jO!mXsNrHD!kk9sG}czt+uO7QaPrz|Zjgh*wB zwy+;R#Mij&EJ0t@%Ik97np}xdLGXNi1;ft7%v3tAcbmzAqUk3)Py#64KdVPqH?k;) zG6|H``QcvoG_RilU*%}dlvq#`=**={g5|2w=~5*aJpcl4sqVn}1y9~5x#fZMGPOu) zQ^T3$_{y^)*30^v(>xOdzjTLJXFe*c?^6G^(K|;ntihosMDIa@2-=hkU ztg@r&P<~N=IPSftdZ~475wTPAZZTTSvtKKVcP#9@TKIfL8!TP*=&` z12ITb-DN525Y6*?P`vxBUfsd;ig&U?B~O(*%qkB$RsE>j>w_>=Za1qOKP*?6x09v9 z4Ws^U>*&UA{d#%4k_4Dk*Wp4aZ8P@DF?(vHG~vBGsN7KD>Pkiyav?%%yp4unHg_R zs(X}O71{B-)Zg(dM~;JIBb3{iyg?VdFoe9k<>k)jv^*1^52vNGO!>3cA9fE{>g7_Q z)GW^pI~Z#^#U%&qvg=%M+(Au$$|;^d)|he5r%R~cI9HQ$mdI{nq7u$_sMlP3qU_ih zq0F6uo6Y;S`kpplBMpXL@Duw<-I8lf*i~e}u9u{;Jo+P!kwqoY=7jxvOcDg6glTV- zQ1C|yPU?Zly=D|tnXoXjNv)I2wE!jyz7q=y-evfd1um zqs_UpGgPZvgF3-#uU_0a0rwDn%g;0OWAXx$YrQTMqgoA;FCXOo{;|2bvrH}BT@JK4 zZ*JrKChDYh!+4!a+*nkrj?PT5hFaI^pk5I=qBrbI@-nY&aUfqBjR|^etUcGnT)yz* z+J#2R^CjT+y3j+*fhJRI2&05wR>TU|>)KFOd05xPyr;_pH|bj4?F%}L%LCu$rZk`B ziqJQ?Ot~S~1@lmWmvK3o5Z{ZqD&ig7(CXtu2Dh}vYKNY@74y?=4xJd%PX~QF!aE5L z8Jg5GOT-;yH<`twzOFXjw9soM1;bV&=CvZ*{Q_PHisq%A6Ur+_p%&o^+8)NXx`LQ& zP0V+*I_jEPpWt}8DDGNW9{5mJhdvZ4-*PPUW{Ca~Z*3O0MAsR2wnrN^E8^&2(v^B( zhW|I}|D!s#>gXho!X?4Y(4#=T!Kgaf3y^eM4DW;6$wT%lG-&!bJmBRISG}srGgXk( zYf{A6IhGMm}HtOBe5tW@o^$#R)>Q>CRxRw!FcXMJ)YyOv{8&Vbfdr(=AjEkt~QIP z@zPahsqw>9wK;JN#5o zj@R0{)(AhFK>x0oV7AYWT+u$&S|3E{>y0Jywu72KO>}vi zB&&T8t*Jjn+-RyDZ4+G-MH%S7IVp+Asa_|T@L;aZm=wNhK6)=_Li0dU^48Vi@#1i0 zhGZCF2Z%~z;u8~V_7d`!_{2mK`NR;Nj}dF1mCMX2R{6P5dg8CHAdw zMP;}$FpJ7iTC3bMV5*6*8u^aV8-hGXx0^;Ev$rVu;v~`Ykjw<)cbbBfYPGB3twEI? z8mmQoc@T3`M5nN4i>aV-Q=0HH>OR!F zl=+CpEs>rh?pnb@<*<$KWYce$o3^PgloIhr7%jR1Da)<3E3$25Q}?JQbbdnFu=C@T z!wN;UeWsnH+k!`yBz&QaOng_;?a&j~tTu>7-d|~q#CEy0>6A$lF-*I(61zo_1hQM? z-AK%K^u3gn;w`CpS6;l`k4dX`U&Y2%n{5*>hml<3O_HSJWh)}9%9a-we+|;@&>|Um z$t8H$zm=PmP_R-;idSWYef88tYg_JwB~0E*%Y%orJt)N~N!G;T!?%x{f!gFE>~6FO zFArkEDvMD@wqYY46J4f$UmaveRpz>e4j)KMpGCalUM~Z`!~T9tn-EbHg%i|GQei=f zNUapRN4(jvo(lu-CKg()34YLvTdOX^tZ*Ja=k+39Z`fEB#L+!@MdTN^ShQ@Y2$h8X zrR_no=<@ksSA)j@({|xnP|u$c67_c&gu3BoQ~L+tTNV3QB1`_Gw<6G3rYTavN$0s) M+|7|_xtRX{17(uE-v9sr literal 0 HcmV?d00001 diff --git a/Jellyfin.Server/obj/Debug/net10.0/Jellyfin.Server.csproj.AssemblyReference.cache b/Jellyfin.Server/obj/Debug/net10.0/Jellyfin.Server.csproj.AssemblyReference.cache new file mode 100644 index 0000000000000000000000000000000000000000..9930e69350bf98024cfb273a74e7f2d5264dbbfc GIT binary patch literal 32681 zcmeHw30#cd_kTmBC`B78vZbPF_64C;Y1Lv&GRspNVM zP)N3{SwpgZ>}&t~JhMDA)R;c?|NXwNe&5GSQ*+OqdCqyCbMCq4+>S{B54hbEv}4&^E(QZWRgLgQCo{ZG3xmOQS0uq`!Oh_6Bz{pt$ieTJS>T8IZ14+s zRm0@LKL?55Spk04gz3TGe*O-4HFcN@qc-RM!mTN} zIfo0%t8FP~c2Me`U?+a`NZ7T$m+m{ZZ_=X42c0r+58r`(LyFn>azv+L`^+vFnUd4B z!cOJTM=jTC{O1zEB13!Dvb4vSN=#ip4$yjEx36DqmlWMKyS^GYR%@P5-Wi+zdHUg8 zztNFf5nd|pHW*qT*0aNmS7daU#RNzoqS}3*bjWJbM;}&4%cB5`oTS;6e$Zun6D_Gg>J|Og4uX%D2N|!52xfp@3fONu6R4$fick(HzAUi;vxPj@lQo8B#79HJE43bU#JRSyjU(*`tg`R<-Bl2p$wr2 z5@1;jRyc4T|?+ zk~==A)3w1@yI@|`-)JoOD`I?{^Rr!}H;#9Ben4Y!T0l)EzVuVOf6^tlk z7Lmeak!TPJBGPa~iICJV{>t&<#tOn2!f;_YLlA9;$C9u(+h_rV#Y+)`b69q1?GTPS3!n=h}X{%HwDwPV09~H&mvAG-`6wMF_Apx*i zI;sIe5olxx*aV;nPGV9|K#J;t--ugxsJCFN?Izcb*sC^xUS_8fHBfX;m^}FHLCOaT z?V>@iGjcCSzUQa>eaq_p*Qx5Kb+RiS^e4_V@~G_E@7ltOs`@*ZVUWdM=cleK|E8TRj>qXF~8=d~Rc7=tg;63CKbv;UlH9hlr z!m%%rzt{_|g`-`K_%CN`__M=XBU#Xm?+7-RW7 z8ds|kVsn3G+!?dRrzyvaoT6q=>grQpyP$=& zryp&Jc3fH8znejpVD^?zmKhFfp}RBY_q-m?`R8fR(@75B&-Lbc7DuhT@g(8nsuYN{ zh^nsfEu}GH_-z9{{11;r=uA48T+$gb`78HeU``dvjGZw!q$aXl!{(_wW<5{rS}+N0-`tIhS~G&Aixp&1V-=?*{3Sbx(HBK8}4=o$fIxUdMON z?dPJgKW=>;GIK!P*y~NtomYuAefeHJuf~_7l|9?a>)4eL*9%QOHy%rP{8|mVWPA@n zrVFicg8{a70>)Q?PZS}QEd)m;(jX>16i*~TY$}-|LrA#;ZA(Ef*xjO}tyiub`T!zc2MI4w0(t;T&{YJ^F%y?RFG&NS3>Ff3+IiA}-*s0WF z6XU9Nzy}O-?()s1*8~B@TThy&gapqC++QUeW~P=9ao#GmQ}GJDfiuSCtXN3hA44@@ z*qT(9>wP(@pD>WL)%M~*%QNBO(~=$*`IE1k4R|tO+{{G_Q9jE%xbdZHjS6_(LH9!cx&|L7_}Ml?4(pp*V;@$1|l8 z!tj)OEd&!RNG$S%Tn@;fF+_YpD`}js_$tcX$|bN8U#PBKohSZxyF?#Z8C_Nz?1F^0dLG>U)$jK2*(E^Y*j-jy%dG z>poY*_w7slvB~e}tEO*H7dQ{49GW}g`o?v0M_nrUtbTIi?owQ)*N$bu z$B%wUSrsFqew}Z#;)ZA6&0`E24vv{<%HKyCyK08E!^DV0x6N<#3bs}@o!wvKR^h(m z)27Ih`?;}Yo>1Vf5^y_=qE_Yc}$SXXz z&~3qp@XnojzRWUQwfYvj{%X{D`t=he!r4W(w(9qncJF-+Ln|seA$;|nd%5q1f2;;! zHtjWha_($l?MJuEX(u0bP|cmrqHPj zI)%AI_(dI}lvAZJ4Z!v%r zCB=M>*j6wuik(r$RUTLsvRAH3l?>Rn5CY*Q7)@ohHFO9?Y+opsvMP%|OimKVok&k4 z?u5C~0sr9whxBTeGkc^D%8>YLvE)|f$^DdbBw$H1!8igLOCf*=PHI8&oOudZzDULx z1u!!Q&niK<)0VYj8oH>d@#Q_5gvDk#bdmK#xg^MLLlvb}z;1$4PNN_ZgDGHecr!ri zF)E7BLz1g9e+?8DXXgcI9K|$n=9kYyxm}f8er5`iWbN@Al`PjP!?RhgF&403Bnfas z@Vi2#6l5^rN~wom)5HS}|CmQRCS(lxT)r;&6h8+_Dm!)T%|U|o>&OwyN=0)_jrv*M z7`og$r>^?@ZOyC(ovy2VmusF}>y&>vdjM&Yb`bOyKQ}s#-*CJzGuh|J;W;7orhUnS z><=D!LnEcHq1;^k`r)cN!I%2{VFn5JG#25Oo4M!unr@yH;d4EtslMlcDTxm>1AJJe zO;stoIiBMNrPjIKf9hNLb%p<({2VSZ{}R_g+vKJ z9Y)M^Y9bZ5O{hFSAhl<=LT}UH(#V~AdUv@L%qlw-lUuab270knPjAJUbXVh5CQS>D zrv6ixS*Ue6KaIv)K-E`z%9~HB=VLRf>OM6!Bw?2XEj|uoq*HBA=)AK-S|LhLH%#Off zGART)3rEBe(7E;W))@>oLI|ve3J>H`7tw;%j01$I=>$E^y(u;wj2gc0J2IHJT*0V4{E=L;f{r2vXZiUSK7 zlrBgIS7`^ul>ucI77|F!Q5Ku&(Q%<=DCN6dAzpy__ZVXtOi-I$Sp9))&0a4zzQ})d;StB4}5ANNtNgv^>JA;3j?@@#pCblPJR=<&qG0?6`^$t_u6@sszcZ zjui5^G{j>=JXT^9pAG%VAE5o5W&OP1eyA_l^z=PN9Pr7*-(p7@{BS^{ zutuQuWzPSs_)i}G7TNy=elS4)Ilt6mICwy1AAsHuSo{hqM?!e@D>fWra7E#)aEKKt z#I`mN<=kh#M6{fV$E@ovz&kTsWP?daMOx3Oq+%Ci_(D- zL>rWDnuS08P# z*L<;Wib#NSwSasD`6R2Ybi$6p;I{>*o(8`#9yhv=CuE;vifJnUYtJcU20417(+XDH8%Ipw zZ@kJOhlo)XJ z7b8_dDZj9SHp`;}+7V?@avQB=P&k!j6Uz^mY8B%#t50h6UU_n< z_0h2L*&^?v^+K0}TLhC{nAG&Vcs~ zGPs>SPfE7@{SxE<3#W46z=BiB;D|T|n-mIBm;?re0YPl3tS}x*RWX{&NaS*aA|Y7Z zjO+!JSsR=}^_8}Iy9A)-=6yfyx-K&2>Btixx~MLPi|iH@0%kF67$^CzatwnFfS{V$ zN^MlWHaMCOR;GYT3k54PHdqCH;kQw)f}bzTvO)5}8c2((Wt2Ra%|GOSjkJ0{a`(xx z96tP5&Ejb~V~f4H-RqpUoHqB}=W@04#m1x~gqo})NAH?vY)hUgw215(`G8bBJ(se5 z(l=;KCGF2c*%f~NA%p9i1ExFP(ZGFLfk&!RD4mre|&h0+LQ z7E1=O@@;I0{KZ*%1rQJoLqJgUbSM&C#TdcO9TyHSl*#tQMESn1XaP7uKokze3TZs(4wzUR+{6hTwOFr7&QlJG>Oe zeNVT@^Jnp9Ss}S<5tGAs>3YysXwZ;eCK&JB;dj)|$W>BjT% zJ_-Yd{*6y(5TyhNW1i0m%@Ap zjrEpqQzMA8cJXEyFSRS5mbL4}>vvP!m^L}ayXG4#?4#+KzD__dbPt+mT=a2tI%|tT zeJM85V9cmPe&y4(^5^&tEjycAK#3bvoM_VXgQh4>uc`9R`6!gy=LpmC2y9$z*g-AVc|>A=3?5cd!g$ zGK-`Qgaiix(zqB_qhF}SJB<9q)TmGTVseXe5AjK6$~s`+7)9kc#b4P+ zq4Gj*xz%xtgHk&K+wwVFei*W=Nw&}m-uvw@Za_KjG|j|s6%sUek+dO9N>fbmINgR| z`CFF357Ia@{{5eGUlUJib$h3|G%l#3QTHF-@q0`9hv~2_gK)RCKMmJ?9%R4wTus*T zs40VYO>MyNt@I6R*O%|L=u_c(YW!TiGf(>1{$SaY$~UAR?6sn|vERKZ6(djS#J>x$ ziynH5gXJw;Hi>Nic7fUq_gi`+e*`uTRtw#|BJBm@1qPLU^4nIwP#-f+!lAtEktq4k zF(ZM*B7{=O1O^Qv(-?TPTs>J89ONAxQnHj`g480UprGCh#m8)2fEN~tEqne>Cd&xI zFu_-cfoj6<%%Z4?MW~9&v#Uet9l|VcJ2c(V#LaZnpY}I5V%eH0M!9l3>HeF6-fc?FyP5-&;{id#SPAHwGB6pO0E+^izVT3O$Qtj zrNo80pww<+Y81!M4ddO!uCxz|qpWBsA@8^@by~|zqo}rnYi%{GAW#WY9ac{vSS95Dw_uep-L9}IKPyWp88Jiof+&Uvwo-V93^-U&1dcoaZbBN8%a56^ zC@t721Jk0QK6|ZpIRo*zGEjdf$H+&iu>0*Wt~Xd}tG_dif~s2LrHnS;rQ@0{Q#s=(%I)BhRSS@W z93EIAfgAw}cEN&7dUH1Gj}ReeU&_HrK6&Y#Vt^z^hwPG-T4sl`Qdxi`$1MsRBEvRK zQi4@8PXrQRn?gPpvI9qV305wid}}sIln%LDH_Q%68S(0B?exF0HY^G zQ1+@&n!)73$V!s2Ev;q$&&mc&YL;IVKSYvk!ml-U$A*}(VJ(l9V)UG0A{0C3^&Rfw zsikaUB1hM8N5^064v`9qTib_4<@FDtB-9ZiW)jK)MhD6ssJoO;vx(tw1vDsXMwyx~ z(~yjtETe;xA=?;XQ<7dC#@qrG%?*|biWwrnHrX1p6}~b|04moYI|?7hB}*2=r7Oi- z94jXKWif@_G;YnC+wd8ZvdB~npFH_*=MS_2#+5E^w|h--JWPF>DY%neO)Y)1Qtw7I z^|9ew=LuKtPj>EShMAFEp)(DaYT|mw(y05|ANHT@>z9ivEOsQmm>IYALO+MpgT;|n z@6YVCQhUKM*G+dxpY(~Z+fbtY4^3x#p2N8z4W7pxhc4vUZr^YrXGOOl4X5P2QfT6LtGdWkMR`-VtJ~ zVA0k0D}Ccb$QLz?MOG&4ps5uTYG|Ep)m?RmkuRp02EILPop4~CF}sTL@O19mt?Q>Q zvfNiPB87Z=p;xsqYp&Nn8Y8vVV7OU!Hy@l1#;V&s@U&R}eT{#K-?r2}2#ESUZuq{s zm9QG~%g`s`7sXFy7mCN>@oX9y2Ud+Rs5Cl?pSN;z4Ch3l=PAP{H_OZr4tMSRLA&fw zWYlvSejFG!9*I?S6(|s(UuQW8kqPF1OlgqxJj+k4yFhFZmpNh&;oqOId zzEi?&cu1Bjot0VSIxTwx}_$(tTm9r_u-(#E`9y2~> z!@axDY<5z@8q*K&WS%*a^x1Ngwx9L4mkZRt=zO^oICAZ^>jm{r8X04q`rX|+slVwJ z1gKj-SN-J9nMCd2sOX@w#>bC=`AQ z01{FDezbLdd5GjJHf(yeQeNVWo$~q4wj(2$rDK^LMh23)jTonQdR(1j?NqvU@n1#T78(vCLQ= zn+t_NA>!>xL}aFomw7+=m7MIlA7@!hg6=Q+;EY!`B}AVUCI6)fy*kjuI22k z?5kFA$d=!=C~M;hpQR}Gz6)`xghq&qLJ>4xD{%Zb9A>Jzh;BhE3^@_F#P znUxKg=`He%!V6Er@pvqOLLmIP$V(#-C=i3p4kc2VECz%wYEix)Kn$e>U}Q0i_*Tt1R9#ZF&B>_htIiVpCUW z*i=^Dyy+3>$O|lgWD>&KNP0SJM&8d%;+?0lolC67^uF_6`=rU5{K<1ev&fB04WcXh zRupGiuANs}vHG0uu9+dzHttKbaw_~jN?W`5`>}=-=@>>@D0M z7A9R=vhkSasG^h4pLWKq|C_x?rLqYpZ{{6poOX$~KT9IvkFaSMSr z7iYeI{-t9*F`<9p{9l4iXAyCDHaPH?!p1S#cy#}Vn{rni2g(w$LJ>a-44uCPouJVP zX+$CX&}c1*IJ^4GnmcZ|>4h?h0)t3OmAQF~v8M6^@q|FI;(eOUWAe*hOq?@80*N+! zw@7QpMZ{CaV|xPP{6{Mhf?%@+8-b%K)It_+AxYrSf^QXW?6_!nNLg%8FqE%$6vl(6 z4l!d{ksyVLE|W zU9-S~>zeq)Agvz{BZjfozBC@(xzCn0p4KPNxbBR6^+wqL0Ine3T;jW8j~*haBM z#H)(uBNHeTBAW%yvjJPzAUqm-C_l`m1#H3FVPW98N>p$ZIzfsql2vXVEWiv*kK(&@ zaQIgUnHKbIfl)~pB-;!Z(WP75+JJDk*_S%KahkE>)W_Q7@75Os?&**D$P!h>40sWI zYtPadQ9q}z?b390-P$d}evdxad2P*EU$=GCiro3Hzk9qG?V4N8OIY+_k>jb-*l9od z{8+=SJoM%^Dexev|AtV*OM3SmbjrJ&dhwE=cRl31Z$E?J)708a>(86pOtc;J*=6l~ z*T2MRN+y&n5-p9*#xRsKj$(`mSrv>a1PYnTrjn>6I*v-FqmN9BRqjmXFq5-ip%}9; z2tEw4RWOf*xB{CA85>Hf+Y2EE!7>>dUOo%XMudOfk9#_ZJvZ}}ID7Ns8=+BA9U zd2{<^^YyyVJ(2aa#+tphe9x^}WbTc8U8w$|#}>QmKVOe=dxyVJ7<_~KNILw zeKS|9P~T|yoR>CFY}EHm_BeN~8`o?R@Z31DFuZwFOnYX_N)mIeE9LK zkJ*{YT#a1>S5LiA|@6GI4k!nGCkbNfTKZVH@y{<+K*=KzG!x zcL?JoXj>@fIMBU4!ESiDTrc%H?Xsgqal)sVLBp7%Po1YkzxThqKa)F&9d}?3H)3t} zeEi3nI_@g-Jzul6h8%aToByCoP*SH&=X>bqB{iN9`5hhgX!f^n+v~iowmyIH^}hDn z(?cf?X6p^_e=^YV?xwzJLWju6=Z%ES@UHe(ZSm=YGl%IqUU=nYo<+WdfbB>)w`|Cs zvH%S0Pptn7tVskSfkmg|2~;MLMIfVD(m#|TkPYEjHc0G97Cga60s~L|i`y2HIZMcX zJr(%g_XEeo8_12sJIt|)lpS~H_@rj}Me&0rsZvI@#VzB1Smxa-{CowRI0iT~5F7@; z=En&v8Dtij zf~ONn5KitHZ^|FVCt`%bCyaxHbTr6hg0hh`T#?Ut1u()qIm}<4-m*ODZfT5ov(qWgcQNt<6jE3LLIkmbqsm8Q6XRcX3#47&L*dKCY;E9cfA9 z#$7L+bA29t?0iO8b#;V3LR4njvt<%zdg zQ7AT+7s=zt!%x_Nv#79EO6rM$qR>Eb8>)trn=(gUL)5S<+Wea){!8xwZw$8nxVP@> z8CIykINi0I^8vWNUt?1UpX^6+sc8}pB-r7mSAW-yzEoxEk?g8vTDlWI&wa@DoyESH z?Bmv1^+om1r&eYzch^~EIy2UwtgH}0W@-PVr}>2GS212FlPD+v5Y>i1IyjJ>%7Um2 z5XMk&Y!npbd7c?05)R7{amD-Vsc@Y{h6@alLx3j=$hoguJwlP%duQ-I5?;!u2BfZ` zS)VW*^g!97!buBNbgdQsUh_xk`vLAHp(2quMbUcmdVl5ZYZb7*SD6_{|5-Z(VGAO literal 0 HcmV?d00001 diff --git a/Jellyfin.Server/obj/Jellyfin.Server.csproj.nuget.dgspec.json b/Jellyfin.Server/obj/Jellyfin.Server.csproj.nuget.dgspec.json new file mode 100644 index 00000000..4c7dfaf5 --- /dev/null +++ b/Jellyfin.Server/obj/Jellyfin.Server.csproj.nuget.dgspec.json @@ -0,0 +1,11815 @@ +{ + "format": 1, + "restore": { + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server/Jellyfin.Server.csproj": {} + }, + "projects": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj", + "projectName": "Jellyfin.Naming", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj", + "projectName": "Emby.Photos", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "TagLibSharp": { + "target": "Package", + "version": "[2.3.0, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/Emby.Server.Implementations.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/Emby.Server.Implementations.csproj", + "projectName": "Emby.Server.Implementations", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/Emby.Server.Implementations.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Photos/Emby.Photos.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Providers/MediaBrowser.Providers.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Providers/MediaBrowser.Providers.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "BitFaster.Caching": { + "target": "Package", + "version": "[2.5.4, )", + "versionCentrallyManaged": true + }, + "DiscUtils.Udf": { + "target": "Package", + "version": "[0.16.13, )", + "versionCentrallyManaged": true + }, + "DotNet.Glob": { + "target": "Package", + "version": "[3.1.3, )", + "versionCentrallyManaged": true + }, + "Ignore": { + "target": "Package", + "version": "[0.2.1, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Data.Sqlite": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Relational": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Caching.Memory": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.DependencyInjection": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "prometheus-net.DotNetRuntime": { + "target": "Package", + "version": "[4.4.1, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj", + "projectName": "Jellyfin.Api", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/Jellyfin.Api.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Api/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.AspNetCore.Authorization": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Http": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "Swashbuckle.AspNetCore": { + "target": "Package", + "version": "[7.3.2, )", + "versionCentrallyManaged": true + }, + "Swashbuckle.AspNetCore.ReDoc": { + "target": "Package", + "version": "[6.9.0, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "projectName": "Jellyfin.Data", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Logging": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj", + "projectName": "Jellyfin.Server.Implementations", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "AsyncKeyedLock": { + "target": "Package", + "version": "[8.0.2, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Relational": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server/Jellyfin.Server.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server/Jellyfin.Server.csproj", + "projectName": "jellyfin", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server/Jellyfin.Server.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/Emby.Server.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/Emby.Server.Implementations.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "CommandLineParser": { + "target": "Package", + "version": "[2.9.1, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Morestachio": { + "target": "Package", + "version": "[5.0.1.631, )", + "versionCentrallyManaged": true + }, + "Serilog.AspNetCore": { + "target": "Package", + "version": "[10.0.0, )", + "versionCentrallyManaged": true + }, + "Serilog.Enrichers.Thread": { + "target": "Package", + "version": "[4.0.0, )", + "versionCentrallyManaged": true + }, + "Serilog.Expressions": { + "target": "Package", + "version": "[5.0.0, )", + "versionCentrallyManaged": true + }, + "Serilog.Settings.Configuration": { + "target": "Package", + "version": "[10.0.0, )", + "versionCentrallyManaged": true + }, + "Serilog.Sinks.Async": { + "target": "Package", + "version": "[2.1.0, )", + "versionCentrallyManaged": true + }, + "Serilog.Sinks.Console": { + "target": "Package", + "version": "[6.1.1, )", + "versionCentrallyManaged": true + }, + "Serilog.Sinks.File": { + "target": "Package", + "version": "[7.0.0, )", + "versionCentrallyManaged": true + }, + "Serilog.Sinks.Graylog": { + "target": "Package", + "version": "[3.1.1, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "prometheus-net": { + "target": "Package", + "version": "[8.2.1, )", + "versionCentrallyManaged": true + }, + "prometheus-net.AspNetCore": { + "target": "Package", + "version": "[8.2.1, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.AspNetCore": "(,10.0.32767]", + "Microsoft.AspNetCore.Antiforgery": "(,10.0.32767]", + "Microsoft.AspNetCore.App": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.BearerToken": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Cookies": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.OAuth": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization.Policy": "(,10.0.32767]", + "Microsoft.AspNetCore.Components": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Endpoints": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Forms": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Server": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Web": "(,10.0.32767]", + "Microsoft.AspNetCore.Connections.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.CookiePolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.Internal": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.AspNetCore.HostFiltering": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Html.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Features": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Results": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpLogging": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpOverrides": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpsPolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Identity": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Metadata": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ApiExplorer": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.DataAnnotations": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Xml": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.RazorPages": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.TagHelpers": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ViewFeatures": "(,10.0.32767]", + "Microsoft.AspNetCore.OutputCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.RateLimiting": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor.Runtime": "(,10.0.32767]", + "Microsoft.AspNetCore.RequestDecompression": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCompression": "(,10.0.32767]", + "Microsoft.AspNetCore.Rewrite": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.HttpSys": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IIS": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IISIntegration": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets": "(,10.0.32767]", + "Microsoft.AspNetCore.Session": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticAssets": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticFiles": "(,10.0.32767]", + "Microsoft.AspNetCore.WebSockets": "(,10.0.32767]", + "Microsoft.AspNetCore.WebUtilities": "(,10.0.32767]", + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.Extensions.Caching.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Caching.Memory": "(,10.0.32767]", + "Microsoft.Extensions.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Binder": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.CommandLine": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.FileExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Ini": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Json": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.KeyPerFile": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.UserSecrets": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Xml": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Features": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Composite": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Physical": "(,10.0.32767]", + "Microsoft.Extensions.FileSystemGlobbing": "(,10.0.32767]", + "Microsoft.Extensions.Hosting": "(,10.0.32767]", + "Microsoft.Extensions.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Http": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Core": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Stores": "(,10.0.32767]", + "Microsoft.Extensions.Localization": "(,10.0.32767]", + "Microsoft.Extensions.Localization.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Console": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Debug": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventLog": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventSource": "(,10.0.32767]", + "Microsoft.Extensions.Logging.TraceSource": "(,10.0.32767]", + "Microsoft.Extensions.ObjectPool": "(,10.0.32767]", + "Microsoft.Extensions.Options": "(,10.0.32767]", + "Microsoft.Extensions.Options.ConfigurationExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Options.DataAnnotations": "(,10.0.32767]", + "Microsoft.Extensions.Primitives": "(,10.0.32767]", + "Microsoft.Extensions.Validation": "(,10.0.32767]", + "Microsoft.Extensions.WebEncoders": "(,10.0.32767]", + "Microsoft.JSInterop": "(,10.0.32767]", + "Microsoft.Net.Http.Headers": "(,10.0.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.EventLog": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Cbor": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Cryptography.Xml": "(,10.0.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.RateLimiting": "(,10.0.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj", + "projectName": "Jellyfin.Common", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.AspNetCore": "(,10.0.32767]", + "Microsoft.AspNetCore.Antiforgery": "(,10.0.32767]", + "Microsoft.AspNetCore.App": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.BearerToken": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Cookies": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.OAuth": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization.Policy": "(,10.0.32767]", + "Microsoft.AspNetCore.Components": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Endpoints": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Forms": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Server": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Web": "(,10.0.32767]", + "Microsoft.AspNetCore.Connections.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.CookiePolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.Internal": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.AspNetCore.HostFiltering": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Html.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Features": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Results": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpLogging": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpOverrides": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpsPolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Identity": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Metadata": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ApiExplorer": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.DataAnnotations": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Xml": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.RazorPages": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.TagHelpers": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ViewFeatures": "(,10.0.32767]", + "Microsoft.AspNetCore.OutputCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.RateLimiting": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor.Runtime": "(,10.0.32767]", + "Microsoft.AspNetCore.RequestDecompression": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCompression": "(,10.0.32767]", + "Microsoft.AspNetCore.Rewrite": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.HttpSys": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IIS": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IISIntegration": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets": "(,10.0.32767]", + "Microsoft.AspNetCore.Session": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticAssets": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticFiles": "(,10.0.32767]", + "Microsoft.AspNetCore.WebSockets": "(,10.0.32767]", + "Microsoft.AspNetCore.WebUtilities": "(,10.0.32767]", + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.Extensions.Caching.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Caching.Memory": "(,10.0.32767]", + "Microsoft.Extensions.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Binder": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.CommandLine": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.FileExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Ini": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Json": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.KeyPerFile": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.UserSecrets": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Xml": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Features": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Composite": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Physical": "(,10.0.32767]", + "Microsoft.Extensions.FileSystemGlobbing": "(,10.0.32767]", + "Microsoft.Extensions.Hosting": "(,10.0.32767]", + "Microsoft.Extensions.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Http": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Core": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Stores": "(,10.0.32767]", + "Microsoft.Extensions.Localization": "(,10.0.32767]", + "Microsoft.Extensions.Localization.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Console": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Debug": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventLog": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventSource": "(,10.0.32767]", + "Microsoft.Extensions.Logging.TraceSource": "(,10.0.32767]", + "Microsoft.Extensions.ObjectPool": "(,10.0.32767]", + "Microsoft.Extensions.Options": "(,10.0.32767]", + "Microsoft.Extensions.Options.ConfigurationExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Options.DataAnnotations": "(,10.0.32767]", + "Microsoft.Extensions.Primitives": "(,10.0.32767]", + "Microsoft.Extensions.Validation": "(,10.0.32767]", + "Microsoft.Extensions.WebEncoders": "(,10.0.32767]", + "Microsoft.JSInterop": "(,10.0.32767]", + "Microsoft.Net.Http.Headers": "(,10.0.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.EventLog": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Cbor": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Cryptography.Xml": "(,10.0.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.RateLimiting": "(,10.0.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj", + "projectName": "Jellyfin.Controller", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Naming/Emby.Naming.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "BitFaster.Caching": { + "target": "Package", + "version": "[2.5.4, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Configuration.Binder": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj", + "projectName": "MediaBrowser.LocalMetadata", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.LocalMetadata/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj", + "projectName": "MediaBrowser.MediaEncoding", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.MediaEncoding/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "AsyncKeyedLock": { + "target": "Package", + "version": "[8.0.2, )", + "versionCentrallyManaged": true + }, + "BDInfo": { + "target": "Package", + "version": "[0.8.0, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Http": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "UTF.Unknown": { + "target": "Package", + "version": "[2.6.0, )", + "versionCentrallyManaged": true + }, + "libse": { + "target": "Package", + "version": "[4.0.12, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj", + "projectName": "Jellyfin.Model", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "MimeTypes": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[2.5.2, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.AspNetCore": "(,10.0.32767]", + "Microsoft.AspNetCore.Antiforgery": "(,10.0.32767]", + "Microsoft.AspNetCore.App": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.BearerToken": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Cookies": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.OAuth": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization.Policy": "(,10.0.32767]", + "Microsoft.AspNetCore.Components": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Endpoints": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Forms": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Server": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Web": "(,10.0.32767]", + "Microsoft.AspNetCore.Connections.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.CookiePolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.Internal": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.AspNetCore.HostFiltering": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Html.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Features": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Results": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpLogging": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpOverrides": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpsPolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Identity": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Metadata": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ApiExplorer": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.DataAnnotations": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Xml": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.RazorPages": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.TagHelpers": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ViewFeatures": "(,10.0.32767]", + "Microsoft.AspNetCore.OutputCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.RateLimiting": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor.Runtime": "(,10.0.32767]", + "Microsoft.AspNetCore.RequestDecompression": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCompression": "(,10.0.32767]", + "Microsoft.AspNetCore.Rewrite": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.HttpSys": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IIS": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IISIntegration": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets": "(,10.0.32767]", + "Microsoft.AspNetCore.Session": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticAssets": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticFiles": "(,10.0.32767]", + "Microsoft.AspNetCore.WebSockets": "(,10.0.32767]", + "Microsoft.AspNetCore.WebUtilities": "(,10.0.32767]", + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.Extensions.Caching.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Caching.Memory": "(,10.0.32767]", + "Microsoft.Extensions.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Binder": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.CommandLine": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.FileExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Ini": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Json": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.KeyPerFile": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.UserSecrets": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Xml": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Features": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Composite": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Physical": "(,10.0.32767]", + "Microsoft.Extensions.FileSystemGlobbing": "(,10.0.32767]", + "Microsoft.Extensions.Hosting": "(,10.0.32767]", + "Microsoft.Extensions.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Http": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Core": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Stores": "(,10.0.32767]", + "Microsoft.Extensions.Localization": "(,10.0.32767]", + "Microsoft.Extensions.Localization.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Console": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Debug": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventLog": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventSource": "(,10.0.32767]", + "Microsoft.Extensions.Logging.TraceSource": "(,10.0.32767]", + "Microsoft.Extensions.ObjectPool": "(,10.0.32767]", + "Microsoft.Extensions.Options": "(,10.0.32767]", + "Microsoft.Extensions.Options.ConfigurationExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Options.DataAnnotations": "(,10.0.32767]", + "Microsoft.Extensions.Primitives": "(,10.0.32767]", + "Microsoft.Extensions.Validation": "(,10.0.32767]", + "Microsoft.Extensions.WebEncoders": "(,10.0.32767]", + "Microsoft.JSInterop": "(,10.0.32767]", + "Microsoft.Net.Http.Headers": "(,10.0.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.EventLog": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Cbor": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Cryptography.Xml": "(,10.0.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.RateLimiting": "(,10.0.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Providers/MediaBrowser.Providers.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Providers/MediaBrowser.Providers.csproj", + "projectName": "MediaBrowser.Providers", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Providers/MediaBrowser.Providers.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Providers/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "AsyncKeyedLock": { + "target": "Package", + "version": "[8.0.2, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "LrcParser": { + "target": "Package", + "version": "[2025.623.0, )", + "versionCentrallyManaged": true + }, + "MetaBrainz.MusicBrainz": { + "target": "Package", + "version": "[8.0.1, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Caching.Abstractions": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Http": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Newtonsoft.Json": { + "target": "Package", + "version": "[13.0.4, )", + "versionCentrallyManaged": true + }, + "PlaylistsNET": { + "target": "Package", + "version": "[1.4.1, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "TMDbLib": { + "target": "Package", + "version": "[2.3.0, )", + "versionCentrallyManaged": true + }, + "z440.atl.core": { + "target": "Package", + "version": "[7.11.0, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj", + "projectName": "MediaBrowser.XbmcMetadata", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.XbmcMetadata/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "projectName": "Jellyfin.CodeAnalysis", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "netstandard2.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "netstandard2.0": { + "targetAlias": "netstandard2.0", + "projectReferences": {} + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "netstandard2.0": { + "targetAlias": "netstandard2.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[3.11.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.CSharp": { + "suppressParent": "All", + "target": "Package", + "version": "[5.0.0, )", + "versionCentrallyManaged": true + }, + "NETStandard.Library": { + "suppressParent": "All", + "target": "Package", + "version": "[2.0.3, )", + "autoReferenced": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/RuntimeIdentifierGraph.json" + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "version": "10.11.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "projectName": "Jellyfin.Database.Implementations", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Design": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Relational": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Tools": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Polly": { + "target": "Package", + "version": "[8.6.5, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj", + "projectName": "Jellyfin.Database.Providers.Sqlite", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Design": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Relational": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Sqlite": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Tools": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj", + "projectName": "Jellyfin.Drawing.Skia", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing.Skia/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "noWarn": [ + "NU1903" + ], + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "BlurHashSharp": { + "target": "Package", + "version": "[1.4.0-pre.1, )", + "versionCentrallyManaged": true + }, + "BlurHashSharp.SkiaSharp": { + "target": "Package", + "version": "[1.4.0-pre.1, )", + "versionCentrallyManaged": true + }, + "HarfBuzzSharp.NativeAssets.Linux": { + "target": "Package", + "version": "[8.3.1.1, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SkiaSharp": { + "target": "Package", + "version": "[3.116.1, 3.116.1]", + "versionCentrallyManaged": true + }, + "SkiaSharp.HarfBuzz": { + "target": "Package", + "version": "[3.116.1, 3.116.1]", + "versionCentrallyManaged": true + }, + "SkiaSharp.NativeAssets.Linux": { + "target": "Package", + "version": "[3.116.1, 3.116.1]", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "Svg.Skia": { + "target": "Package", + "version": "[3.4.1, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj", + "projectName": "Jellyfin.Drawing", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "AsyncKeyedLock": { + "target": "Package", + "version": "[8.0.2, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "projectName": "Jellyfin.Extensions", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "noWarn": [ + "NU5104" + ], + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "Diacritics": { + "target": "Package", + "version": "[4.1.4, )", + "versionCentrallyManaged": true + }, + "ICU4N.Transliterator": { + "target": "Package", + "version": "[60.1.0-alpha.356, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj", + "projectName": "Jellyfin.LiveTv", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.LiveTv/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "AsyncKeyedLock": { + "target": "Package", + "version": "[8.0.2, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Jellyfin.XmlTv": { + "target": "Package", + "version": "[10.8.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj", + "projectName": "Jellyfin.MediaEncoding.Hls", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj": { + "version": "10.11.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj", + "projectName": "Jellyfin.MediaEncoding.Keyframes", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Keyframes/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "NEbml": { + "target": "Package", + "version": "[1.1.0.5, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj", + "projectName": "Jellyfin.Networking", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/Jellyfin.Networking.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Networking/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + } + } +} \ No newline at end of file diff --git a/Jellyfin.Server/obj/Jellyfin.Server.csproj.nuget.g.props b/Jellyfin.Server/obj/Jellyfin.Server.csproj.nuget.g.props new file mode 100644 index 00000000..d475ba69 --- /dev/null +++ b/Jellyfin.Server/obj/Jellyfin.Server.csproj.nuget.g.props @@ -0,0 +1,27 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + /home/wjones/.nuget/packages/ + /home/wjones/.nuget/packages/ + PackageReference + 7.0.0 + + + + + + + + + + /home/wjones/.nuget/packages/microsoft.extensions.apidescription.server/6.0.5 + /home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556 + /home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31 + /home/wjones/.nuget/packages/seriloganalyzer/0.15.0 + /home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0 + /home/wjones/.nuget/packages/idisposableanalyzers/4.0.8 + + \ No newline at end of file diff --git a/Jellyfin.Server/obj/Jellyfin.Server.csproj.nuget.g.targets b/Jellyfin.Server/obj/Jellyfin.Server.csproj.nuget.g.targets new file mode 100644 index 00000000..c4ad6083 --- /dev/null +++ b/Jellyfin.Server/obj/Jellyfin.Server.csproj.nuget.g.targets @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Jellyfin.Server/obj/project.assets.json b/Jellyfin.Server/obj/project.assets.json new file mode 100644 index 00000000..dd5cea5a --- /dev/null +++ b/Jellyfin.Server/obj/project.assets.json @@ -0,0 +1,4949 @@ +{ + "version": 3, + "targets": { + "net10.0": { + "AsyncKeyedLock/8.0.2": { + "type": "package", + "compile": { + "lib/net9.0/AsyncKeyedLock.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/AsyncKeyedLock.dll": { + "related": ".xml" + } + } + }, + "BDInfo/0.8.0": { + "type": "package", + "compile": { + "lib/net6.0/BDInfo.dll": {} + }, + "runtime": { + "lib/net6.0/BDInfo.dll": {} + } + }, + "BitFaster.Caching/2.5.4": { + "type": "package", + "compile": { + "lib/net6.0/BitFaster.Caching.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/BitFaster.Caching.dll": { + "related": ".xml" + } + } + }, + "BlurHashSharp/1.4.0-pre.1": { + "type": "package", + "compile": { + "lib/net8.0/BlurHashSharp.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net8.0/BlurHashSharp.dll": { + "related": ".pdb;.xml" + } + } + }, + "BlurHashSharp.SkiaSharp/1.4.0-pre.1": { + "type": "package", + "dependencies": { + "BlurHashSharp": "1.4.0-pre.1", + "SkiaSharp": "3.116.1" + }, + "compile": { + "lib/net8.0/BlurHashSharp.SkiaSharp.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net8.0/BlurHashSharp.SkiaSharp.dll": { + "related": ".pdb;.xml" + } + } + }, + "CommandLineParser/2.9.1": { + "type": "package", + "compile": { + "lib/netstandard2.0/CommandLine.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/CommandLine.dll": { + "related": ".xml" + } + } + }, + "Diacritics/4.1.4": { + "type": "package", + "compile": { + "lib/net9.0/Diacritics.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Diacritics.dll": { + "related": ".xml" + } + } + }, + "DiscUtils.Core/0.16.13": { + "type": "package", + "dependencies": { + "DiscUtils.Streams": "0.16.13" + }, + "compile": { + "lib/net5.0/DiscUtils.Core.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net5.0/DiscUtils.Core.dll": { + "related": ".xml" + } + } + }, + "DiscUtils.Iso9660/0.16.13": { + "type": "package", + "dependencies": { + "DiscUtils.Core": "0.16.13" + }, + "compile": { + "lib/net5.0/DiscUtils.Iso9660.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net5.0/DiscUtils.Iso9660.dll": { + "related": ".xml" + } + } + }, + "DiscUtils.Streams/0.16.13": { + "type": "package", + "compile": { + "lib/net5.0/DiscUtils.Streams.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net5.0/DiscUtils.Streams.dll": { + "related": ".xml" + } + } + }, + "DiscUtils.Udf/0.16.13": { + "type": "package", + "dependencies": { + "DiscUtils.Core": "0.16.13", + "DiscUtils.Iso9660": "0.16.13" + }, + "compile": { + "lib/net5.0/DiscUtils.Udf.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net5.0/DiscUtils.Udf.dll": { + "related": ".xml" + } + } + }, + "DotNet.Glob/3.1.3": { + "type": "package", + "compile": { + "lib/netcoreapp2.1/DotNet.Glob.dll": {} + }, + "runtime": { + "lib/netcoreapp2.1/DotNet.Glob.dll": {} + } + }, + "ExCSS/4.3.1": { + "type": "package", + "compile": { + "lib/net8.0/ExCSS.dll": {} + }, + "runtime": { + "lib/net8.0/ExCSS.dll": {} + } + }, + "HarfBuzzSharp/8.3.0.1": { + "type": "package", + "dependencies": { + "HarfBuzzSharp.NativeAssets.Win32": "8.3.0.1", + "HarfBuzzSharp.NativeAssets.macOS": "8.3.0.1" + }, + "compile": { + "lib/net8.0/HarfBuzzSharp.dll": { + "related": ".pdb" + } + }, + "runtime": { + "lib/net8.0/HarfBuzzSharp.dll": { + "related": ".pdb" + } + } + }, + "HarfBuzzSharp.NativeAssets.Linux/8.3.1.1": { + "type": "package", + "compile": { + "lib/net8.0/_._": {} + }, + "runtime": { + "lib/net8.0/_._": {} + }, + "runtimeTargets": { + "runtimes/linux-arm/native/libHarfBuzzSharp.so": { + "assetType": "native", + "rid": "linux-arm" + }, + "runtimes/linux-arm64/native/libHarfBuzzSharp.so": { + "assetType": "native", + "rid": "linux-arm64" + }, + "runtimes/linux-loongarch64/native/libHarfBuzzSharp.so": { + "assetType": "native", + "rid": "linux-loongarch64" + }, + "runtimes/linux-musl-arm/native/libHarfBuzzSharp.so": { + "assetType": "native", + "rid": "linux-musl-arm" + }, + "runtimes/linux-musl-arm64/native/libHarfBuzzSharp.so": { + "assetType": "native", + "rid": "linux-musl-arm64" + }, + "runtimes/linux-musl-loongarch64/native/libHarfBuzzSharp.so": { + "assetType": "native", + "rid": "linux-musl-loongarch64" + }, + "runtimes/linux-musl-riscv64/native/libHarfBuzzSharp.so": { + "assetType": "native", + "rid": "linux-musl-riscv64" + }, + "runtimes/linux-musl-x64/native/libHarfBuzzSharp.so": { + "assetType": "native", + "rid": "linux-musl-x64" + }, + "runtimes/linux-riscv64/native/libHarfBuzzSharp.so": { + "assetType": "native", + "rid": "linux-riscv64" + }, + "runtimes/linux-x64/native/libHarfBuzzSharp.so": { + "assetType": "native", + "rid": "linux-x64" + }, + "runtimes/linux-x86/native/libHarfBuzzSharp.so": { + "assetType": "native", + "rid": "linux-x86" + } + } + }, + "HarfBuzzSharp.NativeAssets.macOS/8.3.0.1": { + "type": "package", + "compile": { + "lib/net8.0/_._": {} + }, + "runtime": { + "lib/net8.0/_._": {} + }, + "runtimeTargets": { + "runtimes/osx/native/libHarfBuzzSharp.dylib": { + "assetType": "native", + "rid": "osx" + } + } + }, + "HarfBuzzSharp.NativeAssets.Win32/8.3.0.1": { + "type": "package", + "compile": { + "lib/net8.0/_._": {} + }, + "runtime": { + "lib/net8.0/_._": {} + }, + "runtimeTargets": { + "runtimes/win-arm64/native/libHarfBuzzSharp.dll": { + "assetType": "native", + "rid": "win-arm64" + }, + "runtimes/win-x64/native/libHarfBuzzSharp.dll": { + "assetType": "native", + "rid": "win-x64" + }, + "runtimes/win-x86/native/libHarfBuzzSharp.dll": { + "assetType": "native", + "rid": "win-x86" + } + } + }, + "ICU4N/60.1.0-alpha.356": { + "type": "package", + "dependencies": { + "J2N": "2.0.0" + }, + "compile": { + "lib/netstandard2.0/ICU4N.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/ICU4N.dll": { + "related": ".xml" + } + } + }, + "ICU4N.Transliterator/60.1.0-alpha.356": { + "type": "package", + "dependencies": { + "ICU4N": "60.1.0-alpha.356" + }, + "compile": { + "lib/netstandard2.0/ICU4N.Transliterator.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/ICU4N.Transliterator.dll": { + "related": ".xml" + } + } + }, + "IDisposableAnalyzers/4.0.8": { + "type": "package" + }, + "Ignore/0.2.1": { + "type": "package", + "compile": { + "lib/net8.0/Ignore.dll": {} + }, + "runtime": { + "lib/net8.0/Ignore.dll": {} + } + }, + "J2N/2.0.0": { + "type": "package", + "compile": { + "lib/net6.0/J2N.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/J2N.dll": { + "related": ".xml" + } + } + }, + "Jellyfin.XmlTv/10.8.0": { + "type": "package", + "compile": { + "lib/net6.0/Jellyfin.XmlTv.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/Jellyfin.XmlTv.dll": { + "related": ".xml" + } + } + }, + "libse/4.0.12": { + "type": "package", + "dependencies": { + "System.Drawing.Common": "9.0.2", + "UTF.Unknown": "2.5.1", + "zlib.net-mutliplatform": "1.0.8" + }, + "compile": { + "lib/netstandard2.1/libse.dll": {} + }, + "runtime": { + "lib/netstandard2.1/libse.dll": {} + }, + "contentFiles": { + "contentFiles/any/any/_._": { + "buildAction": "None", + "codeLanguage": "any", + "copyToOutput": false + } + } + }, + "LrcParser/2025.623.0": { + "type": "package", + "compile": { + "lib/netstandard2.1/LrcParser.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.1/LrcParser.dll": { + "related": ".xml" + } + } + }, + "MetaBrainz.Common/4.1.1": { + "type": "package", + "compile": { + "lib/net10.0/MetaBrainz.Common.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/MetaBrainz.Common.dll": { + "related": ".xml" + } + } + }, + "MetaBrainz.Common.Json/7.2.0": { + "type": "package", + "dependencies": { + "MetaBrainz.Common": "4.1.1" + }, + "compile": { + "lib/net10.0/MetaBrainz.Common.Json.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/MetaBrainz.Common.Json.dll": { + "related": ".xml" + } + } + }, + "MetaBrainz.MusicBrainz/8.0.1": { + "type": "package", + "dependencies": { + "MetaBrainz.Common": "4.1.1", + "MetaBrainz.Common.Json": "7.2.0" + }, + "compile": { + "lib/net10.0/MetaBrainz.MusicBrainz.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/MetaBrainz.MusicBrainz.dll": { + "related": ".xml" + } + } + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers/4.14.0": { + "type": "package", + "build": { + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props": {}, + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets": {} + } + }, + "Microsoft.Data.Sqlite/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Data.Sqlite.Core": "10.0.3", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.11", + "SQLitePCLRaw.core": "2.1.11" + }, + "compile": { + "lib/netstandard2.0/_._": {} + }, + "runtime": { + "lib/netstandard2.0/_._": {} + } + }, + "Microsoft.Data.Sqlite.Core/10.0.3": { + "type": "package", + "dependencies": { + "SQLitePCLRaw.core": "2.1.11" + }, + "compile": { + "lib/net8.0/Microsoft.Data.Sqlite.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Microsoft.Data.Sqlite.dll": { + "related": ".xml" + } + } + }, + "Microsoft.EntityFrameworkCore/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "10.0.3", + "Microsoft.EntityFrameworkCore.Analyzers": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props": {} + } + }, + "Microsoft.EntityFrameworkCore.Abstractions/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + } + }, + "Microsoft.EntityFrameworkCore.Analyzers/10.0.3": { + "type": "package" + }, + "Microsoft.EntityFrameworkCore.Relational/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + } + }, + "Microsoft.EntityFrameworkCore.Sqlite/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore.Sqlite.Core": "10.0.3", + "Microsoft.Extensions.DependencyModel": "10.0.3", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.11", + "SQLitePCLRaw.core": "2.1.11" + }, + "compile": { + "lib/net10.0/_._": {} + }, + "runtime": { + "lib/net10.0/_._": {} + } + }, + "Microsoft.EntityFrameworkCore.Sqlite.Core/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Data.Sqlite.Core": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.Extensions.DependencyModel": "10.0.3", + "SQLitePCLRaw.core": "2.1.11" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Sqlite.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Sqlite.dll": { + "related": ".xml" + } + } + }, + "Microsoft.Extensions.ApiDescription.Server/6.0.5": { + "type": "package", + "build": { + "build/_._": {} + }, + "buildMultiTargeting": { + "buildMultiTargeting/_._": {} + } + }, + "Microsoft.Extensions.DependencyModel/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.Extensions.DependencyModel.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.DependencyModel.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore.Relational": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.dll": { + "related": ".xml" + } + } + }, + "Microsoft.OpenApi/1.6.22": { + "type": "package", + "compile": { + "lib/netstandard2.0/Microsoft.OpenApi.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/netstandard2.0/Microsoft.OpenApi.dll": { + "related": ".pdb;.xml" + } + } + }, + "Microsoft.Win32.SystemEvents/9.0.2": { + "type": "package", + "compile": { + "lib/net9.0/Microsoft.Win32.SystemEvents.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Win32.SystemEvents.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + }, + "runtimeTargets": { + "runtimes/win/lib/net9.0/Microsoft.Win32.SystemEvents.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "Morestachio/5.0.1.631": { + "type": "package", + "compile": { + "lib/net6.0/Morestachio.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net6.0/Morestachio.dll": { + "related": ".pdb;.xml" + } + } + }, + "NEbml/1.1.0.5": { + "type": "package", + "compile": { + "lib/netstandard2.0/NEbml.Core.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/NEbml.Core.dll": { + "related": ".xml" + } + } + }, + "Newtonsoft.Json/13.0.4": { + "type": "package", + "compile": { + "lib/net6.0/Newtonsoft.Json.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/Newtonsoft.Json.dll": { + "related": ".xml" + } + } + }, + "PlaylistsNET/1.4.1": { + "type": "package", + "compile": { + "lib/net8.0/PlaylistsNET.dll": {} + }, + "runtime": { + "lib/net8.0/PlaylistsNET.dll": {} + } + }, + "Polly/8.6.5": { + "type": "package", + "dependencies": { + "Polly.Core": "8.6.5" + }, + "compile": { + "lib/net6.0/Polly.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net6.0/Polly.dll": { + "related": ".pdb;.xml" + } + } + }, + "Polly.Core/8.6.5": { + "type": "package", + "compile": { + "lib/net8.0/Polly.Core.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net8.0/Polly.Core.dll": { + "related": ".pdb;.xml" + } + } + }, + "prometheus-net/8.2.1": { + "type": "package", + "compile": { + "lib/net7.0/Prometheus.NetStandard.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/Prometheus.NetStandard.dll": { + "related": ".xml" + } + } + }, + "prometheus-net.AspNetCore/8.2.1": { + "type": "package", + "dependencies": { + "prometheus-net": "8.2.1" + }, + "compile": { + "lib/net6.0/Prometheus.AspNetCore.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/Prometheus.AspNetCore.dll": { + "related": ".xml" + } + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "prometheus-net.DotNetRuntime/4.4.1": { + "type": "package", + "dependencies": { + "prometheus-net": "3.1.2" + }, + "compile": { + "lib/net6.0/prometheus-net.DotNetRuntime.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net6.0/prometheus-net.DotNetRuntime.dll": { + "related": ".pdb;.xml" + } + } + }, + "Serilog/4.3.0": { + "type": "package", + "compile": { + "lib/net9.0/Serilog.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Serilog.dll": { + "related": ".xml" + } + }, + "build": { + "build/_._": {} + } + }, + "Serilog.AspNetCore/10.0.0": { + "type": "package", + "dependencies": { + "Serilog": "4.3.0", + "Serilog.Extensions.Hosting": "10.0.0", + "Serilog.Formatting.Compact": "3.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.Debug": "3.0.0", + "Serilog.Sinks.File": "7.0.0" + }, + "compile": { + "lib/net10.0/Serilog.AspNetCore.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Serilog.AspNetCore.dll": { + "related": ".xml" + } + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Serilog.Enrichers.Thread/4.0.0": { + "type": "package", + "dependencies": { + "Serilog": "4.0.0" + }, + "compile": { + "lib/net8.0/Serilog.Enrichers.Thread.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Serilog.Enrichers.Thread.dll": { + "related": ".xml" + } + } + }, + "Serilog.Expressions/5.0.0": { + "type": "package", + "dependencies": { + "Serilog": "4.0.0" + }, + "compile": { + "lib/net8.0/Serilog.Expressions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Serilog.Expressions.dll": { + "related": ".xml" + } + } + }, + "Serilog.Extensions.Hosting/10.0.0": { + "type": "package", + "dependencies": { + "Serilog": "4.3.0", + "Serilog.Extensions.Logging": "10.0.0" + }, + "compile": { + "lib/net10.0/Serilog.Extensions.Hosting.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Serilog.Extensions.Hosting.dll": { + "related": ".xml" + } + } + }, + "Serilog.Extensions.Logging/10.0.0": { + "type": "package", + "dependencies": { + "Serilog": "4.2.0" + }, + "compile": { + "lib/net10.0/Serilog.Extensions.Logging.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Serilog.Extensions.Logging.dll": { + "related": ".xml" + } + } + }, + "Serilog.Formatting.Compact/3.0.0": { + "type": "package", + "dependencies": { + "Serilog": "4.0.0" + }, + "compile": { + "lib/net8.0/Serilog.Formatting.Compact.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Serilog.Formatting.Compact.dll": { + "related": ".xml" + } + } + }, + "Serilog.Settings.Configuration/10.0.0": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyModel": "10.0.0", + "Serilog": "4.3.0" + }, + "compile": { + "lib/net10.0/Serilog.Settings.Configuration.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Serilog.Settings.Configuration.dll": { + "related": ".xml" + } + } + }, + "Serilog.Sinks.Async/2.1.0": { + "type": "package", + "dependencies": { + "Serilog": "4.1.0" + }, + "compile": { + "lib/net8.0/Serilog.Sinks.Async.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Serilog.Sinks.Async.dll": { + "related": ".xml" + } + } + }, + "Serilog.Sinks.Console/6.1.1": { + "type": "package", + "dependencies": { + "Serilog": "4.0.0" + }, + "compile": { + "lib/net8.0/Serilog.Sinks.Console.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Serilog.Sinks.Console.dll": { + "related": ".xml" + } + } + }, + "Serilog.Sinks.Debug/3.0.0": { + "type": "package", + "dependencies": { + "Serilog": "4.0.0" + }, + "compile": { + "lib/net8.0/Serilog.Sinks.Debug.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Serilog.Sinks.Debug.dll": { + "related": ".xml" + } + } + }, + "Serilog.Sinks.File/7.0.0": { + "type": "package", + "dependencies": { + "Serilog": "4.2.0" + }, + "compile": { + "lib/net9.0/Serilog.Sinks.File.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Serilog.Sinks.File.dll": { + "related": ".xml" + } + } + }, + "Serilog.Sinks.Graylog/3.1.1": { + "type": "package", + "compile": { + "lib/net7.0/Serilog.Sinks.Graylog.Core.dll": {}, + "lib/net7.0/Serilog.Sinks.Graylog.dll": {} + }, + "runtime": { + "lib/net7.0/Serilog.Sinks.Graylog.Core.dll": {}, + "lib/net7.0/Serilog.Sinks.Graylog.dll": {} + } + }, + "SerilogAnalyzer/0.15.0": { + "type": "package" + }, + "ShimSkiaSharp/3.4.1": { + "type": "package", + "compile": { + "lib/net10.0/ShimSkiaSharp.dll": {} + }, + "runtime": { + "lib/net10.0/ShimSkiaSharp.dll": {} + } + }, + "SkiaSharp/3.116.1": { + "type": "package", + "dependencies": { + "SkiaSharp.NativeAssets.Win32": "3.116.1", + "SkiaSharp.NativeAssets.macOS": "3.116.1" + }, + "compile": { + "ref/net8.0/SkiaSharp.dll": {} + }, + "runtime": { + "lib/net8.0/SkiaSharp.dll": { + "related": ".pdb" + } + } + }, + "SkiaSharp.HarfBuzz/3.116.1": { + "type": "package", + "dependencies": { + "HarfBuzzSharp": "8.3.0.1", + "SkiaSharp": "3.116.1" + }, + "compile": { + "lib/net8.0/SkiaSharp.HarfBuzz.dll": { + "related": ".pdb" + } + }, + "runtime": { + "lib/net8.0/SkiaSharp.HarfBuzz.dll": { + "related": ".pdb" + } + } + }, + "SkiaSharp.NativeAssets.Linux/3.116.1": { + "type": "package", + "compile": { + "lib/net8.0/_._": {} + }, + "runtime": { + "lib/net8.0/_._": {} + }, + "runtimeTargets": { + "runtimes/linux-arm/native/libSkiaSharp.so": { + "assetType": "native", + "rid": "linux-arm" + }, + "runtimes/linux-arm/native/libSkiaSharp.so.116.0.0": { + "assetType": "native", + "rid": "linux-arm" + }, + "runtimes/linux-arm64/native/libSkiaSharp.so": { + "assetType": "native", + "rid": "linux-arm64" + }, + "runtimes/linux-arm64/native/libSkiaSharp.so.116.0.0": { + "assetType": "native", + "rid": "linux-arm64" + }, + "runtimes/linux-musl-x64/native/libSkiaSharp.so": { + "assetType": "native", + "rid": "linux-musl-x64" + }, + "runtimes/linux-musl-x64/native/libSkiaSharp.so.116.0.0": { + "assetType": "native", + "rid": "linux-musl-x64" + }, + "runtimes/linux-x64/native/libSkiaSharp.so": { + "assetType": "native", + "rid": "linux-x64" + }, + "runtimes/linux-x64/native/libSkiaSharp.so.116.0.0": { + "assetType": "native", + "rid": "linux-x64" + } + } + }, + "SkiaSharp.NativeAssets.macOS/3.116.1": { + "type": "package", + "compile": { + "lib/net8.0/_._": {} + }, + "runtime": { + "lib/net8.0/_._": {} + }, + "runtimeTargets": { + "runtimes/osx/native/libSkiaSharp.dylib": { + "assetType": "native", + "rid": "osx" + } + } + }, + "SkiaSharp.NativeAssets.Win32/3.116.1": { + "type": "package", + "compile": { + "lib/net8.0/_._": {} + }, + "runtime": { + "lib/net8.0/_._": {} + }, + "runtimeTargets": { + "runtimes/win-arm64/native/libSkiaSharp.dll": { + "assetType": "native", + "rid": "win-arm64" + }, + "runtimes/win-x64/native/libSkiaSharp.dll": { + "assetType": "native", + "rid": "win-x64" + }, + "runtimes/win-x86/native/libSkiaSharp.dll": { + "assetType": "native", + "rid": "win-x86" + } + } + }, + "SmartAnalyzers.MultithreadingAnalyzer/1.1.31": { + "type": "package" + }, + "SQLitePCLRaw.bundle_e_sqlite3/2.1.11": { + "type": "package", + "dependencies": { + "SQLitePCLRaw.lib.e_sqlite3": "2.1.11", + "SQLitePCLRaw.provider.e_sqlite3": "2.1.11" + }, + "compile": { + "lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll": {} + }, + "runtime": { + "lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll": {} + } + }, + "SQLitePCLRaw.core/2.1.11": { + "type": "package", + "compile": { + "lib/netstandard2.0/SQLitePCLRaw.core.dll": {} + }, + "runtime": { + "lib/netstandard2.0/SQLitePCLRaw.core.dll": {} + } + }, + "SQLitePCLRaw.lib.e_sqlite3/2.1.11": { + "type": "package", + "compile": { + "lib/netstandard2.0/_._": {} + }, + "runtime": { + "lib/netstandard2.0/_._": {} + }, + "build": { + "buildTransitive/net9.0/SQLitePCLRaw.lib.e_sqlite3.targets": {} + }, + "runtimeTargets": { + "runtimes/browser-wasm/nativeassets/net9.0/e_sqlite3.a": { + "assetType": "native", + "rid": "browser-wasm" + }, + "runtimes/linux-arm/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-arm" + }, + "runtimes/linux-arm64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-arm64" + }, + "runtimes/linux-armel/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-armel" + }, + "runtimes/linux-mips64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-mips64" + }, + "runtimes/linux-musl-arm/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-arm" + }, + "runtimes/linux-musl-arm64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-arm64" + }, + "runtimes/linux-musl-riscv64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-riscv64" + }, + "runtimes/linux-musl-s390x/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-s390x" + }, + "runtimes/linux-musl-x64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-x64" + }, + "runtimes/linux-ppc64le/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-ppc64le" + }, + "runtimes/linux-riscv64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-riscv64" + }, + "runtimes/linux-s390x/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-s390x" + }, + "runtimes/linux-x64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-x64" + }, + "runtimes/linux-x86/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-x86" + }, + "runtimes/maccatalyst-arm64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "maccatalyst-arm64" + }, + "runtimes/maccatalyst-x64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "maccatalyst-x64" + }, + "runtimes/osx-arm64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "osx-arm64" + }, + "runtimes/osx-x64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "osx-x64" + }, + "runtimes/win-arm/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-arm" + }, + "runtimes/win-arm64/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-arm64" + }, + "runtimes/win-x64/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-x64" + }, + "runtimes/win-x86/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-x86" + } + } + }, + "SQLitePCLRaw.provider.e_sqlite3/2.1.11": { + "type": "package", + "dependencies": { + "SQLitePCLRaw.core": "2.1.11" + }, + "compile": { + "lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll": {} + }, + "runtime": { + "lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll": {} + } + }, + "StyleCop.Analyzers/1.2.0-beta.556": { + "type": "package", + "dependencies": { + "StyleCop.Analyzers.Unstable": "1.2.0.556" + } + }, + "StyleCop.Analyzers.Unstable/1.2.0.556": { + "type": "package" + }, + "Svg.Custom/3.4.1": { + "type": "package", + "dependencies": { + "ExCSS": "4.3.1" + }, + "compile": { + "lib/net10.0/Svg.Custom.dll": {} + }, + "runtime": { + "lib/net10.0/Svg.Custom.dll": {} + } + }, + "Svg.Model/3.4.1": { + "type": "package", + "dependencies": { + "ShimSkiaSharp": "3.4.1", + "Svg.Custom": "3.4.1" + }, + "compile": { + "lib/net10.0/Svg.Model.dll": {} + }, + "runtime": { + "lib/net10.0/Svg.Model.dll": {} + } + }, + "Svg.Skia/3.4.1": { + "type": "package", + "dependencies": { + "SkiaSharp": "2.88.9", + "Svg.Custom": "3.4.1", + "Svg.Model": "3.4.1" + }, + "compile": { + "lib/net10.0/Svg.Skia.dll": {} + }, + "runtime": { + "lib/net10.0/Svg.Skia.dll": {} + } + }, + "Swashbuckle.AspNetCore/7.3.2": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.ApiDescription.Server": "6.0.5", + "Swashbuckle.AspNetCore.Swagger": "7.3.2", + "Swashbuckle.AspNetCore.SwaggerGen": "7.3.2", + "Swashbuckle.AspNetCore.SwaggerUI": "7.3.2" + }, + "build": { + "build/_._": {} + }, + "buildMultiTargeting": { + "buildMultiTargeting/_._": {} + } + }, + "Swashbuckle.AspNetCore.ReDoc/6.9.0": { + "type": "package", + "compile": { + "lib/net8.0/Swashbuckle.AspNetCore.ReDoc.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net8.0/Swashbuckle.AspNetCore.ReDoc.dll": { + "related": ".pdb;.xml" + } + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Swashbuckle.AspNetCore.Swagger/7.3.2": { + "type": "package", + "dependencies": { + "Microsoft.OpenApi": "1.6.22" + }, + "compile": { + "lib/net9.0/Swashbuckle.AspNetCore.Swagger.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net9.0/Swashbuckle.AspNetCore.Swagger.dll": { + "related": ".pdb;.xml" + } + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Swashbuckle.AspNetCore.SwaggerGen/7.3.2": { + "type": "package", + "dependencies": { + "Swashbuckle.AspNetCore.Swagger": "7.3.2" + }, + "compile": { + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll": { + "related": ".pdb;.xml" + } + } + }, + "Swashbuckle.AspNetCore.SwaggerUI/7.3.2": { + "type": "package", + "compile": { + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll": { + "related": ".pdb;.xml" + } + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "System.Drawing.Common/9.0.2": { + "type": "package", + "dependencies": { + "Microsoft.Win32.SystemEvents": "9.0.2" + }, + "compile": { + "lib/net9.0/System.Drawing.Common.dll": { + "related": ".pdb;.xml" + }, + "lib/net9.0/System.Private.Windows.Core.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/System.Drawing.Common.dll": { + "related": ".pdb;.xml" + }, + "lib/net9.0/System.Private.Windows.Core.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "TagLibSharp/2.3.0": { + "type": "package", + "compile": { + "lib/netstandard2.0/TagLibSharp.dll": { + "related": ".pdb" + } + }, + "runtime": { + "lib/netstandard2.0/TagLibSharp.dll": { + "related": ".pdb" + } + } + }, + "TMDbLib/2.3.0": { + "type": "package", + "dependencies": { + "Newtonsoft.Json": "13.0.3" + }, + "compile": { + "lib/netstandard2.1/TMDbLib.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.1/TMDbLib.dll": { + "related": ".xml" + } + } + }, + "Ude.NetStandard/1.2.0": { + "type": "package", + "compile": { + "lib/netstandard2.0/Ude.NetStandard.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Ude.NetStandard.dll": {} + } + }, + "UTF.Unknown/2.6.0": { + "type": "package", + "compile": { + "lib/net8.0/UtfUnknown.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/UtfUnknown.dll": { + "related": ".xml" + } + } + }, + "z440.atl.core/7.11.0": { + "type": "package", + "dependencies": { + "Ude.NetStandard": "1.2.0" + }, + "compile": { + "lib/net6.0/ATL.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/ATL.dll": { + "related": ".xml" + } + } + }, + "zlib.net-mutliplatform/1.0.8": { + "type": "package", + "compile": { + "lib/netstandard2.1/zlib.net.dll": {} + }, + "runtime": { + "lib/netstandard2.1/zlib.net.dll": {} + } + }, + "Emby.Photos/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.Model": "10.12.0", + "TagLibSharp": "2.3.0" + }, + "compile": { + "bin/placeholder/Emby.Photos.dll": {} + }, + "runtime": { + "bin/placeholder/Emby.Photos.dll": {} + } + }, + "Emby.Server.Implementations/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "BitFaster.Caching": "2.5.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "Emby.Photos": "1.0.0", + "Ignore": "0.2.1", + "Jellyfin.Api": "1.0.0", + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.Database.Implementations": "10.11.0", + "Jellyfin.Drawing": "1.0.0", + "Jellyfin.Model": "10.12.0", + "Jellyfin.Naming": "10.12.0", + "Jellyfin.Server.Implementations": "1.0.0", + "MediaBrowser.LocalMetadata": "1.0.0", + "MediaBrowser.MediaEncoding": "1.0.0", + "MediaBrowser.Providers": "1.0.0", + "MediaBrowser.XbmcMetadata": "1.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "prometheus-net.DotNetRuntime": "4.4.1" + }, + "compile": { + "bin/placeholder/Emby.Server.Implementations.dll": {} + }, + "runtime": { + "bin/placeholder/Emby.Server.Implementations.dll": {} + } + }, + "Jellyfin.Api/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.MediaEncoding.Hls": "1.0.0", + "Jellyfin.Networking": "1.0.0", + "MediaBrowser.MediaEncoding": "1.0.0", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Api.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Api.dll": {} + } + }, + "Jellyfin.CodeAnalysis/1.0.0": { + "type": "project", + "framework": ".NETStandard,Version=v2.0", + "compile": { + "bin/placeholder/Jellyfin.CodeAnalysis.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.CodeAnalysis.dll": {} + } + }, + "Jellyfin.Common/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Common.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Common.dll": {} + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Jellyfin.Controller/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "BitFaster.Caching": "2.5.4", + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.MediaEncoding.Keyframes": "10.11.0", + "Jellyfin.Model": "10.12.0", + "Jellyfin.Naming": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Controller.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Controller.dll": {} + } + }, + "Jellyfin.Data/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Database.Implementations": "10.11.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Data.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Data.dll": {} + } + }, + "Jellyfin.Database.Implementations/10.11.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Polly": "8.6.5" + }, + "compile": { + "bin/placeholder/Jellyfin.Database.Implementations.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Database.Implementations.dll": {} + } + }, + "Jellyfin.Database.Providers.Sqlite/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Database.Implementations": "10.11.0", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3" + }, + "compile": { + "bin/placeholder/Jellyfin.Database.Providers.Sqlite.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Database.Providers.Sqlite.dll": {} + } + }, + "Jellyfin.Drawing/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "AsyncKeyedLock": "8.0.2", + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Drawing.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Drawing.dll": {} + } + }, + "Jellyfin.Drawing.Skia/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.Model": "10.12.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "Svg.Skia": "3.4.1" + }, + "compile": { + "bin/placeholder/Jellyfin.Drawing.Skia.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Drawing.Skia.dll": {} + } + }, + "Jellyfin.Extensions/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Diacritics": "4.1.4", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "Jellyfin.CodeAnalysis": "1.0.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Extensions.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Extensions.dll": {} + } + }, + "Jellyfin.LiveTv/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "AsyncKeyedLock": "8.0.2", + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.Model": "10.12.0", + "Jellyfin.XmlTv": "10.8.0" + }, + "compile": { + "bin/placeholder/Jellyfin.LiveTv.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.LiveTv.dll": {} + } + }, + "Jellyfin.MediaEncoding.Hls/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.MediaEncoding.Keyframes": "10.11.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.MediaEncoding.Hls.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.MediaEncoding.Hls.dll": {} + } + }, + "Jellyfin.MediaEncoding.Keyframes/10.11.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "NEbml": "1.1.0.5" + }, + "compile": { + "bin/placeholder/Jellyfin.MediaEncoding.Keyframes.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.MediaEncoding.Keyframes.dll": {} + } + }, + "Jellyfin.Model/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Data": "10.12.0", + "Jellyfin.Extensions": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Model.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Model.dll": {} + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Jellyfin.Naming/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Naming.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Naming.dll": {} + } + }, + "Jellyfin.Networking/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Controller": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Networking.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Networking.dll": {} + } + }, + "Jellyfin.Server.Implementations/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "AsyncKeyedLock": "8.0.2", + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.Data": "10.12.0", + "Jellyfin.Database.Implementations": "10.11.0", + "Jellyfin.Database.Providers.Sqlite": "1.0.0", + "Jellyfin.Model": "10.12.0", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3" + }, + "compile": { + "bin/placeholder/Jellyfin.Server.Implementations.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Server.Implementations.dll": {} + } + }, + "MediaBrowser.LocalMetadata/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/MediaBrowser.LocalMetadata.dll": {} + }, + "runtime": { + "bin/placeholder/MediaBrowser.LocalMetadata.dll": {} + } + }, + "MediaBrowser.MediaEncoding/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "AsyncKeyedLock": "8.0.2", + "BDInfo": "0.8.0", + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Common": "10.12.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.Model": "10.12.0", + "UTF.Unknown": "2.6.0", + "libse": "4.0.12" + }, + "compile": { + "bin/placeholder/MediaBrowser.MediaEncoding.dll": {} + }, + "runtime": { + "bin/placeholder/MediaBrowser.MediaEncoding.dll": {} + } + }, + "MediaBrowser.Providers/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "AsyncKeyedLock": "8.0.2", + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.Model": "10.12.0", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "TMDbLib": "2.3.0", + "z440.atl.core": "7.11.0" + }, + "compile": { + "bin/placeholder/MediaBrowser.Providers.dll": {} + }, + "runtime": { + "bin/placeholder/MediaBrowser.Providers.dll": {} + } + }, + "MediaBrowser.XbmcMetadata/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Controller": "10.12.0", + "Jellyfin.Model": "10.12.0" + }, + "compile": { + "bin/placeholder/MediaBrowser.XbmcMetadata.dll": {} + }, + "runtime": { + "bin/placeholder/MediaBrowser.XbmcMetadata.dll": {} + } + } + } + }, + "libraries": { + "AsyncKeyedLock/8.0.2": { + "sha512": "QGys5cnIerNryv7V14PDkvGnlLz69kJtTfdnr+Lndcu+lRre397RNyU4FIeAJWgI9u73lTzXL52Qca9B/ncLXw==", + "type": "package", + "path": "asynckeyedlock/8.0.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "asynckeyedlock.8.0.2.nupkg.sha512", + "asynckeyedlock.nuspec", + "lib/net5.0/AsyncKeyedLock.dll", + "lib/net5.0/AsyncKeyedLock.xml", + "lib/net6.0/AsyncKeyedLock.dll", + "lib/net6.0/AsyncKeyedLock.xml", + "lib/net7.0/AsyncKeyedLock.dll", + "lib/net7.0/AsyncKeyedLock.xml", + "lib/net8.0/AsyncKeyedLock.dll", + "lib/net8.0/AsyncKeyedLock.xml", + "lib/net9.0/AsyncKeyedLock.dll", + "lib/net9.0/AsyncKeyedLock.xml", + "lib/netstandard2.0/AsyncKeyedLock.dll", + "lib/netstandard2.0/AsyncKeyedLock.xml", + "lib/netstandard2.1/AsyncKeyedLock.dll", + "lib/netstandard2.1/AsyncKeyedLock.xml", + "logo.png" + ] + }, + "BDInfo/0.8.0": { + "sha512": "9pz06aM0ceknGQ410IqqO0176ZPp2JPX00ekDDICaiU5sVh2qThOIAEfID08QWPS0xoM/JYtVsO42uDVf+08MQ==", + "type": "package", + "path": "bdinfo/0.8.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "bdinfo.0.8.0.nupkg.sha512", + "bdinfo.nuspec", + "lib/net6.0/BDInfo.dll", + "lib/netstandard2.1/BDInfo.dll" + ] + }, + "BitFaster.Caching/2.5.4": { + "sha512": "1QroTY1PVCZOSG9FnkkCrmCKk/+bZCgI/YXq376HnYwUDJ4Ho0EaV4YaA/5v5WYLnwIwIO7RZkdWbg9pxIpueQ==", + "type": "package", + "path": "bitfaster.caching/2.5.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "ReadMe.md", + "bitfaster.caching.2.5.4.nupkg.sha512", + "bitfaster.caching.nuspec", + "lib/net6.0/BitFaster.Caching.dll", + "lib/net6.0/BitFaster.Caching.xml", + "lib/netcoreapp3.1/BitFaster.Caching.dll", + "lib/netcoreapp3.1/BitFaster.Caching.xml", + "lib/netstandard2.0/BitFaster.Caching.dll", + "lib/netstandard2.0/BitFaster.Caching.xml" + ] + }, + "BlurHashSharp/1.4.0-pre.1": { + "sha512": "f+nvz5shCnn+Y9St2u6ps/iDD3x6HKRKDDMyO/Kb2Qa04aLV00MIObOi40hkuWfeNdh7fsvjJGWFHjcvm2P8Dg==", + "type": "package", + "path": "blurhashsharp/1.4.0-pre.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "blurhashsharp.1.4.0-pre.1.nupkg.sha512", + "blurhashsharp.nuspec", + "lib/net8.0/BlurHashSharp.dll", + "lib/net8.0/BlurHashSharp.pdb", + "lib/net8.0/BlurHashSharp.xml", + "src/BlurHashSharp/AbsMaxExtensions.cs", + "src/BlurHashSharp/BlurHashSharp.csproj", + "src/BlurHashSharp/CoreBlurHashEncoder.cs", + "src/BlurHashSharp/PixelFormat.cs", + "src/BlurHashSharp/ScaleHelper.cs" + ] + }, + "BlurHashSharp.SkiaSharp/1.4.0-pre.1": { + "sha512": "r5ux6Wj0hwPMed6neAxg90X+R8IQBPgbuZL2daUhN/SJWLbp9ghkgs0X50aABY9P4HKq6AwKJhKUso/tQ5oOAQ==", + "type": "package", + "path": "blurhashsharp.skiasharp/1.4.0-pre.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "blurhashsharp.skiasharp.1.4.0-pre.1.nupkg.sha512", + "blurhashsharp.skiasharp.nuspec", + "lib/net8.0/BlurHashSharp.SkiaSharp.dll", + "lib/net8.0/BlurHashSharp.SkiaSharp.pdb", + "lib/net8.0/BlurHashSharp.SkiaSharp.xml", + "src/BlurHashSharp.SkiaSharp/BlurHashEncoder.cs", + "src/BlurHashSharp.SkiaSharp/BlurHashSharp.SkiaSharp.csproj" + ] + }, + "CommandLineParser/2.9.1": { + "sha512": "OE0sl1/sQ37bjVsPKKtwQlWDgqaxWgtme3xZz7JssWUzg5JpMIyHgCTY9MVMxOg48fJ1AgGT3tgdH5m/kQ5xhA==", + "type": "package", + "path": "commandlineparser/2.9.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "CommandLine20.png", + "License.md", + "README.md", + "commandlineparser.2.9.1.nupkg.sha512", + "commandlineparser.nuspec", + "lib/net40/CommandLine.dll", + "lib/net40/CommandLine.xml", + "lib/net45/CommandLine.dll", + "lib/net45/CommandLine.xml", + "lib/net461/CommandLine.dll", + "lib/net461/CommandLine.xml", + "lib/netstandard2.0/CommandLine.dll", + "lib/netstandard2.0/CommandLine.xml" + ] + }, + "Diacritics/4.1.4": { + "sha512": "9JK2bI+oWnzOK4Lu+5FXt7czofD28AMnZ95np5fkSClqioPnga1JVpjaUxVJaEpc/5HIlfikc6YI33lm2qu4mw==", + "type": "package", + "path": "diacritics/4.1.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "README.md", + "diacritics.4.1.4.nupkg.sha512", + "diacritics.nuspec", + "lib/net48/Diacritics.dll", + "lib/net48/Diacritics.xml", + "lib/net7.0/Diacritics.dll", + "lib/net7.0/Diacritics.xml", + "lib/net8.0/Diacritics.dll", + "lib/net8.0/Diacritics.xml", + "lib/net9.0/Diacritics.dll", + "lib/net9.0/Diacritics.xml", + "lib/netstandard1.2/Diacritics.dll", + "lib/netstandard1.2/Diacritics.xml", + "lib/netstandard2.0/Diacritics.dll", + "lib/netstandard2.0/Diacritics.xml", + "lib/netstandard2.1/Diacritics.dll", + "lib/netstandard2.1/Diacritics.xml", + "logo.png" + ] + }, + "DiscUtils.Core/0.16.13": { + "sha512": "TU/Ns5RMa3060K7AsSDqG94/H7eaWaz1XoYuCUur40Mg+t0/ubtKqxCffkoiB1e/cFMGvHL0evFoY6uDL9Ocmw==", + "type": "package", + "path": "discutils.core/0.16.13", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "discutils.core.0.16.13.nupkg.sha512", + "discutils.core.nuspec", + "lib/net40/DiscUtils.Core.dll", + "lib/net40/DiscUtils.Core.xml", + "lib/net45/DiscUtils.Core.dll", + "lib/net45/DiscUtils.Core.xml", + "lib/net5.0/DiscUtils.Core.dll", + "lib/net5.0/DiscUtils.Core.xml", + "lib/netstandard2.0/DiscUtils.Core.dll", + "lib/netstandard2.0/DiscUtils.Core.xml" + ] + }, + "DiscUtils.Iso9660/0.16.13": { + "sha512": "hT0DbN+sIqg5Bu3/sCQUuKLwIrceF9BZvn7AeRe+f2iY0p4mgN9xEmXpFWQshnBBxN9r75364agBHDeb5owiUA==", + "type": "package", + "path": "discutils.iso9660/0.16.13", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "discutils.iso9660.0.16.13.nupkg.sha512", + "discutils.iso9660.nuspec", + "lib/net40/DiscUtils.Iso9660.dll", + "lib/net40/DiscUtils.Iso9660.xml", + "lib/net45/DiscUtils.Iso9660.dll", + "lib/net45/DiscUtils.Iso9660.xml", + "lib/net5.0/DiscUtils.Iso9660.dll", + "lib/net5.0/DiscUtils.Iso9660.xml", + "lib/netstandard2.0/DiscUtils.Iso9660.dll", + "lib/netstandard2.0/DiscUtils.Iso9660.xml" + ] + }, + "DiscUtils.Streams/0.16.13": { + "sha512": "BSrywAX2mmvt6j00u6VQYQLtxc6jDniK2aP4p0q20bbTGXImtW4N26eUZYKfkW9aj6XLaqISFgtWfssKURixeA==", + "type": "package", + "path": "discutils.streams/0.16.13", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "discutils.streams.0.16.13.nupkg.sha512", + "discutils.streams.nuspec", + "lib/net40/DiscUtils.Streams.dll", + "lib/net40/DiscUtils.Streams.xml", + "lib/net45/DiscUtils.Streams.dll", + "lib/net45/DiscUtils.Streams.xml", + "lib/net5.0/DiscUtils.Streams.dll", + "lib/net5.0/DiscUtils.Streams.xml", + "lib/netstandard2.0/DiscUtils.Streams.dll", + "lib/netstandard2.0/DiscUtils.Streams.xml" + ] + }, + "DiscUtils.Udf/0.16.13": { + "sha512": "wl8amQNP3N5VFB4wlLh76OXD2vGpFKuYb/RqqcwTPdE406mvQsLuxxPJB5E4orkjNDrpglLvi94WVr8FfWY2Uw==", + "type": "package", + "path": "discutils.udf/0.16.13", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "discutils.udf.0.16.13.nupkg.sha512", + "discutils.udf.nuspec", + "lib/net40/DiscUtils.Udf.dll", + "lib/net40/DiscUtils.Udf.xml", + "lib/net45/DiscUtils.Udf.dll", + "lib/net45/DiscUtils.Udf.xml", + "lib/net5.0/DiscUtils.Udf.dll", + "lib/net5.0/DiscUtils.Udf.xml", + "lib/netstandard2.0/DiscUtils.Udf.dll", + "lib/netstandard2.0/DiscUtils.Udf.xml" + ] + }, + "DotNet.Glob/3.1.3": { + "sha512": "hOfHw7MLJw/tbXaFwR1oiDb+dIXDp8URTxp5Pco42OOhiw77wrUNx6v6syNygHZbWwYdXQocL2Mo1l5FnfDVjg==", + "type": "package", + "path": "dotnet.glob/3.1.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "dotnet.glob.3.1.3.nupkg.sha512", + "dotnet.glob.nuspec", + "lib/net40/DotNet.Glob.dll", + "lib/net45/DotNet.Glob.dll", + "lib/net46/DotNet.Glob.dll", + "lib/netcoreapp2.1/DotNet.Glob.dll", + "lib/netstandard1.1/DotNet.Glob.dll", + "lib/netstandard2.1/DotNet.Glob.dll" + ] + }, + "ExCSS/4.3.1": { + "sha512": "A8UtcghHQlWxPJhJF5bsfEYO7068JHGTuJ7tzyyMjd5fE/N/xY1cVOUhzuLLFwMOCJVnM4mqSQc93W9fGfZ1kA==", + "type": "package", + "path": "excss/4.3.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "excss.4.3.1.nupkg.sha512", + "excss.nuspec", + "lib/net48/ExCSS.dll", + "lib/net6.0/ExCSS.dll", + "lib/net7.0/ExCSS.dll", + "lib/net8.0/ExCSS.dll", + "lib/netcoreapp3.1/ExCSS.dll", + "lib/netstandard2.0/ExCSS.dll", + "lib/netstandard2.1/ExCSS.dll", + "readme.md" + ] + }, + "HarfBuzzSharp/8.3.0.1": { + "sha512": "rwLpl+W6uqu0DuvzqNhTMuFcXfy1Vc0uq0YXgPEmtTSfeUSAye1FcARrm2YIPOSiCBwBOGu3cLvMX5Fp6OKe2g==", + "type": "package", + "path": "harfbuzzsharp/8.3.0.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "README.md", + "harfbuzzsharp.8.3.0.1.nupkg.sha512", + "harfbuzzsharp.nuspec", + "icon.png", + "lib/net462/HarfBuzzSharp.dll", + "lib/net462/HarfBuzzSharp.pdb", + "lib/net6.0/HarfBuzzSharp.dll", + "lib/net6.0/HarfBuzzSharp.pdb", + "lib/net8.0-android34.0/HarfBuzzSharp.dll", + "lib/net8.0-android34.0/HarfBuzzSharp.pdb", + "lib/net8.0-android34.0/HarfBuzzSharp.xml", + "lib/net8.0-ios17.0/HarfBuzzSharp.dll", + "lib/net8.0-ios17.0/HarfBuzzSharp.pdb", + "lib/net8.0-maccatalyst17.0/HarfBuzzSharp.dll", + "lib/net8.0-maccatalyst17.0/HarfBuzzSharp.pdb", + "lib/net8.0-macos14.0/HarfBuzzSharp.dll", + "lib/net8.0-macos14.0/HarfBuzzSharp.pdb", + "lib/net8.0-tizen7.0/HarfBuzzSharp.dll", + "lib/net8.0-tizen7.0/HarfBuzzSharp.pdb", + "lib/net8.0-tvos17.0/HarfBuzzSharp.dll", + "lib/net8.0-tvos17.0/HarfBuzzSharp.pdb", + "lib/net8.0-windows10.0.19041/HarfBuzzSharp.dll", + "lib/net8.0-windows10.0.19041/HarfBuzzSharp.pdb", + "lib/net8.0/HarfBuzzSharp.dll", + "lib/net8.0/HarfBuzzSharp.pdb", + "lib/netstandard2.0/HarfBuzzSharp.dll", + "lib/netstandard2.0/HarfBuzzSharp.pdb", + "lib/netstandard2.1/HarfBuzzSharp.dll", + "lib/netstandard2.1/HarfBuzzSharp.pdb" + ] + }, + "HarfBuzzSharp.NativeAssets.Linux/8.3.1.1": { + "sha512": "3EZ1mpIiKWRLL5hUYA82ZHteeDIVaEA/Z0rA/wU6tjx6crcAkJnBPwDXZugBSfo8+J3EznvRJf49uMsqYfKrHg==", + "type": "package", + "path": "harfbuzzsharp.nativeassets.linux/8.3.1.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "README.md", + "THIRD-PARTY-NOTICES.txt", + "buildTransitive/net462/HarfBuzzSharp.NativeAssets.Linux.targets", + "harfbuzzsharp.nativeassets.linux.8.3.1.1.nupkg.sha512", + "harfbuzzsharp.nativeassets.linux.nuspec", + "icon.png", + "lib/net462/_._", + "lib/net6.0/_._", + "lib/net8.0/_._", + "lib/netstandard2.0/_._", + "lib/netstandard2.1/_._", + "runtimes/linux-arm/native/libHarfBuzzSharp.so", + "runtimes/linux-arm64/native/libHarfBuzzSharp.so", + "runtimes/linux-loongarch64/native/libHarfBuzzSharp.so", + "runtimes/linux-musl-arm/native/libHarfBuzzSharp.so", + "runtimes/linux-musl-arm64/native/libHarfBuzzSharp.so", + "runtimes/linux-musl-loongarch64/native/libHarfBuzzSharp.so", + "runtimes/linux-musl-riscv64/native/libHarfBuzzSharp.so", + "runtimes/linux-musl-x64/native/libHarfBuzzSharp.so", + "runtimes/linux-riscv64/native/libHarfBuzzSharp.so", + "runtimes/linux-x64/native/libHarfBuzzSharp.so", + "runtimes/linux-x86/native/libHarfBuzzSharp.so" + ] + }, + "HarfBuzzSharp.NativeAssets.macOS/8.3.0.1": { + "sha512": "2o6U05LAmK+rwX7TvmJ2X0anXJG2hSE7kHVmCshhHy0tKfByJ5ykBacvhmmooHchlOwq15KBZeROGafCT8nN+g==", + "type": "package", + "path": "harfbuzzsharp.nativeassets.macos/8.3.0.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "README.md", + "THIRD-PARTY-NOTICES.txt", + "buildTransitive/net462/HarfBuzzSharp.NativeAssets.macOS.targets", + "buildTransitive/net8.0-macos14.0/HarfBuzzSharp.NativeAssets.macOS.targets", + "harfbuzzsharp.nativeassets.macos.8.3.0.1.nupkg.sha512", + "harfbuzzsharp.nativeassets.macos.nuspec", + "icon.png", + "lib/net462/_._", + "lib/net6.0/_._", + "lib/net8.0-macos14.0/_._", + "lib/net8.0/_._", + "lib/netstandard2.0/_._", + "lib/netstandard2.1/_._", + "runtimes/osx/native/libHarfBuzzSharp.dylib" + ] + }, + "HarfBuzzSharp.NativeAssets.Win32/8.3.0.1": { + "sha512": "ow0DtGEUjo65qhiI22of7qiVbN1xDFsZ5P5xJljRmGZ5WSxNy+1batLNJFGxahqhB1MTHYV8kAXf0GqC8WaevQ==", + "type": "package", + "path": "harfbuzzsharp.nativeassets.win32/8.3.0.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "README.md", + "THIRD-PARTY-NOTICES.txt", + "buildTransitive/net462/HarfBuzzSharp.NativeAssets.Win32.targets", + "harfbuzzsharp.nativeassets.win32.8.3.0.1.nupkg.sha512", + "harfbuzzsharp.nativeassets.win32.nuspec", + "icon.png", + "lib/net462/_._", + "lib/net6.0-windows10.0.19041/_._", + "lib/net6.0/_._", + "lib/net8.0-windows10.0.19041/_._", + "lib/net8.0/_._", + "lib/netstandard2.0/_._", + "lib/netstandard2.1/_._", + "runtimes/win-arm64/native/libHarfBuzzSharp.dll", + "runtimes/win-x64/native/libHarfBuzzSharp.dll", + "runtimes/win-x86/native/libHarfBuzzSharp.dll" + ] + }, + "ICU4N/60.1.0-alpha.356": { + "sha512": "YMZtDnjcqWzziOKiE7w6Ma7Rl5vuFDxzOsUlHh1QyfghbNEIZQOLRs9MMfwCWAjX6n9UitrF6vLXy55Z5q+4Fg==", + "type": "package", + "path": "icu4n/60.1.0-alpha.356", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "icu4n.60.1.0-alpha.356.nupkg.sha512", + "icu4n.nuspec", + "lib/net40/ICU4N.dll", + "lib/net40/ICU4N.xml", + "lib/net451/ICU4N.dll", + "lib/net451/ICU4N.xml", + "lib/netstandard2.0/ICU4N.dll", + "lib/netstandard2.0/ICU4N.xml", + "unicode-icon-128x128.jpg" + ] + }, + "ICU4N.Transliterator/60.1.0-alpha.356": { + "sha512": "lFOSO6bbEtB6HkWMNDJAq+rFwVyi9g6xVc5O/2xHa6iZnV7wLVDqCbaQ4W4vIeBSQZAafqhxciaEkmAvSdzlCg==", + "type": "package", + "path": "icu4n.transliterator/60.1.0-alpha.356", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "icu4n.transliterator.60.1.0-alpha.356.nupkg.sha512", + "icu4n.transliterator.nuspec", + "lib/net40/ICU4N.Transliterator.dll", + "lib/net40/ICU4N.Transliterator.xml", + "lib/net451/ICU4N.Transliterator.dll", + "lib/net451/ICU4N.Transliterator.xml", + "lib/netstandard2.0/ICU4N.Transliterator.dll", + "lib/netstandard2.0/ICU4N.Transliterator.xml", + "unicode-icon-128x128.jpg" + ] + }, + "IDisposableAnalyzers/4.0.8": { + "sha512": "vNi4NMG0CcJyjXxiNDcQ21FwV/whM9o9OEZKD+oP7tuxAqFEzX/x5OhC3OZJqW/w+8GOtCmJPBquYgMWgz0rfQ==", + "type": "package", + "path": "idisposableanalyzers/4.0.8", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/IDisposableAnalyzers.dll", + "idisposableanalyzers.4.0.8.nupkg.sha512", + "idisposableanalyzers.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Ignore/0.2.1": { + "sha512": "Qw3s0pTwK3o6Iv6kTMjmxzOt91pczU533OmtAvFRsJ7PdCVMhGCRyUkMsCOI7ejxOtHJdRsj141HeZWeedlqkQ==", + "type": "package", + "path": "ignore/0.2.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ignore.0.2.1.nupkg.sha512", + "ignore.nuspec", + "lib/net8.0/Ignore.dll", + "lib/netstandard2.0/Ignore.dll" + ] + }, + "J2N/2.0.0": { + "sha512": "M5bwDajAARZiyqupU+rHQJnsVLxNBOHJ8vKYHd8LcLIb1FgLfzzcJvc31Qo5Xz/GEHFjDF9ScjKL/ks/zRTXuA==", + "type": "package", + "path": "j2n/2.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "j2n-icon-100x100.png", + "j2n.2.0.0.nupkg.sha512", + "j2n.nuspec", + "lib/net40/J2N.dll", + "lib/net40/J2N.xml", + "lib/net45/J2N.dll", + "lib/net45/J2N.xml", + "lib/net5.0/J2N.dll", + "lib/net5.0/J2N.xml", + "lib/net6.0/J2N.dll", + "lib/net6.0/J2N.xml", + "lib/netcoreapp3.0/J2N.dll", + "lib/netcoreapp3.0/J2N.xml", + "lib/netstandard2.0/J2N.dll", + "lib/netstandard2.0/J2N.xml", + "lib/netstandard2.1/J2N.dll", + "lib/netstandard2.1/J2N.xml", + "readme.md" + ] + }, + "Jellyfin.XmlTv/10.8.0": { + "sha512": "jF6lA20jIE7qXJdwZqdIhOS0gEt5WIMEzN1f5jxn1kHWPrabxR1v9vyPGaI3LzN/eSxFCCCudiTErP3HiYOdzw==", + "type": "package", + "path": "jellyfin.xmltv/10.8.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "jellyfin.xmltv.10.8.0.nupkg.sha512", + "jellyfin.xmltv.nuspec", + "lib/net6.0/Jellyfin.XmlTv.dll", + "lib/net6.0/Jellyfin.XmlTv.xml", + "lib/netstandard2.1/Jellyfin.XmlTv.dll", + "lib/netstandard2.1/Jellyfin.XmlTv.xml" + ] + }, + "libse/4.0.12": { + "sha512": "vI4V5rwg5by28SFZXHPciIinwZ4khaE5Y5SS/rTzKxlqxrdmx2RLUrCReejjRfgtVZfvWFKnODTmr2kQ6Qnl2Q==", + "type": "package", + "path": "libse/4.0.12", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.txt", + "Readme.md", + "content/Icon.png", + "contentFiles/any/net48/Icon.png", + "contentFiles/any/netstandard2.1/Icon.png", + "lib/net48/libse.dll", + "lib/netstandard2.1/libse.dll", + "libse.4.0.12.nupkg.sha512", + "libse.nuspec" + ] + }, + "LrcParser/2025.623.0": { + "sha512": "gJRCaoVeYfN36HAGzav75T/Q6NsfYEkK4p6PiVTF6R/NU/JL9U6ZXOw1dodYe81GzaQpef/JNX0/drtJ6ZofMA==", + "type": "package", + "path": "lrcparser/2025.623.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/netstandard2.1/LrcParser.dll", + "lib/netstandard2.1/LrcParser.xml", + "lrcparser.2025.623.0.nupkg.sha512", + "lrcparser.nuspec" + ] + }, + "MetaBrainz.Common/4.1.1": { + "sha512": "ovKr4BktLkKdGcMkKJihcGNPqvsPW4DXRmi051SbJICauVkKgnZFI51uWaHoQoO8Z6YBTqLzqam/GfRlMj0xcw==", + "type": "package", + "path": "metabrainz.common/4.1.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.md", + "README.md", + "lib/net10.0/MetaBrainz.Common.dll", + "lib/net10.0/MetaBrainz.Common.xml", + "lib/net8.0/MetaBrainz.Common.dll", + "lib/net8.0/MetaBrainz.Common.xml", + "metabrainz.common.4.1.1.nupkg.sha512", + "metabrainz.common.nuspec", + "package-icon.png" + ] + }, + "MetaBrainz.Common.Json/7.2.0": { + "sha512": "e3lPRMTujYYzBzZI7GV3s4OwXdBViGGrdpvWf5J/PXFaasjEdkQvKpmwG26PplZ3atPRK8x3FRVgP4lOARAoLQ==", + "type": "package", + "path": "metabrainz.common.json/7.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.md", + "README.md", + "lib/net10.0/MetaBrainz.Common.Json.dll", + "lib/net10.0/MetaBrainz.Common.Json.xml", + "lib/net8.0/MetaBrainz.Common.Json.dll", + "lib/net8.0/MetaBrainz.Common.Json.xml", + "metabrainz.common.json.7.2.0.nupkg.sha512", + "metabrainz.common.json.nuspec", + "package-icon.png" + ] + }, + "MetaBrainz.MusicBrainz/8.0.1": { + "sha512": "CXyfOQbYyICPTOckS169V4+zmk71sd2z6FncoQxE3uR1fvwyD5Q7+Iv5/mAmqnb/u6IyepMt+2+O8lzDLmXZbg==", + "type": "package", + "path": "metabrainz.musicbrainz/8.0.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.md", + "README.md", + "lib/net10.0/MetaBrainz.MusicBrainz.dll", + "lib/net10.0/MetaBrainz.MusicBrainz.xml", + "lib/net8.0/MetaBrainz.MusicBrainz.dll", + "lib/net8.0/MetaBrainz.MusicBrainz.xml", + "metabrainz.musicbrainz.8.0.1.nupkg.sha512", + "metabrainz.musicbrainz.nuspec", + "package-icon.png" + ] + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers/4.14.0": { + "sha512": "gSWJlDWwmDhtbrEJGiHqvEjz9KthIiFD0qYB8zZ6a7z+xpMSPEtM9yTYELSa58iFWYlzSRqP9FXO6KoT3+ZMtg==", + "type": "package", + "path": "microsoft.codeanalysis.bannedapianalyzers/4.14.0", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.txt", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.dll", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.CSharp.BannedApiAnalyzers.dll", + "analyzers/dotnet/cs/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/de/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/es/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/fr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/it/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ja/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ko/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/pl/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/pt-BR/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ru/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/tr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hans/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hant/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.BannedApiAnalyzers.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.VisualBasic.BannedApiAnalyzers.dll", + "analyzers/dotnet/vb/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/de/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/es/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/fr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/it/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ja/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ko/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/pl/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/pt-BR/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ru/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/tr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hans/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hant/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props", + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets", + "documentation/Microsoft.CodeAnalysis.BannedApiAnalyzers.md", + "documentation/Microsoft.CodeAnalysis.BannedApiAnalyzers.sarif", + "editorconfig/AllRulesDefault/.editorconfig", + "editorconfig/AllRulesDisabled/.editorconfig", + "editorconfig/AllRulesEnabled/.editorconfig", + "editorconfig/ApiDesignRulesDefault/.editorconfig", + "editorconfig/ApiDesignRulesEnabled/.editorconfig", + "editorconfig/DataflowRulesDefault/.editorconfig", + "editorconfig/DataflowRulesEnabled/.editorconfig", + "editorconfig/PortedFromFxCopRulesDefault/.editorconfig", + "editorconfig/PortedFromFxCopRulesEnabled/.editorconfig", + "microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512", + "microsoft.codeanalysis.bannedapianalyzers.nuspec", + "rulesets/AllRulesDefault.ruleset", + "rulesets/AllRulesDisabled.ruleset", + "rulesets/AllRulesEnabled.ruleset", + "rulesets/ApiDesignRulesDefault.ruleset", + "rulesets/ApiDesignRulesEnabled.ruleset", + "rulesets/DataflowRulesDefault.ruleset", + "rulesets/DataflowRulesEnabled.ruleset", + "rulesets/PortedFromFxCopRulesDefault.ruleset", + "rulesets/PortedFromFxCopRulesEnabled.ruleset", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Microsoft.Data.Sqlite/10.0.3": { + "sha512": "R/7g5hddFTzey+XOH7Hb3x71DdX5yDu3YqKDJbyn3QKiV6P38bYqkhsoDotanurh5mHLRc9hITE+pxL6Kjcpzw==", + "type": "package", + "path": "microsoft.data.sqlite/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/netstandard2.0/_._", + "microsoft.data.sqlite.10.0.3.nupkg.sha512", + "microsoft.data.sqlite.nuspec" + ] + }, + "Microsoft.Data.Sqlite.Core/10.0.3": { + "sha512": "onD94qHlvteS2S9eg1T7Huehm3x92no4nU1AyDWjSmT6jDBhY8QF0UapwNhFA+1dArzxM10fV7s6uI1EIK2+dw==", + "type": "package", + "path": "microsoft.data.sqlite.core/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net8.0/Microsoft.Data.Sqlite.dll", + "lib/net8.0/Microsoft.Data.Sqlite.xml", + "lib/netstandard2.0/Microsoft.Data.Sqlite.dll", + "lib/netstandard2.0/Microsoft.Data.Sqlite.xml", + "microsoft.data.sqlite.core.10.0.3.nupkg.sha512", + "microsoft.data.sqlite.core.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore/10.0.3": { + "sha512": "CDEImwD4A7BseABJMCpLZnhfFjmPY/bHwhhS70elc6gLI/bYUEOhxWt7PmaNGYGhIEzOnStlCy5QcVb+8dod5Q==", + "type": "package", + "path": "microsoft.entityframeworkcore/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props", + "lib/net10.0/Microsoft.EntityFrameworkCore.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.xml", + "microsoft.entityframeworkcore.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Abstractions/10.0.3": { + "sha512": "c7Uoz381xnMHNBRB8eHRhGgzUtXbgddlbODhwZRrTSzZWDharp3RkJsFwhxyESbeXhCqmML7VdvjMQ7uu+HreA==", + "type": "package", + "path": "microsoft.entityframeworkcore.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.xml", + "microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.abstractions.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Analyzers/10.0.3": { + "sha512": "lVABgJTyTUNE7Bi0bSu4dWHiCHIXEGzTh/kLh0N07IgU/tIKwTeBPp8tgV4x2iKj4h7iPLo8oXzyHmLDGtAE1g==", + "type": "package", + "path": "microsoft.entityframeworkcore.analyzers/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "analyzers/dotnet/cs/Microsoft.EntityFrameworkCore.Analyzers.dll", + "docs/PACKAGE.md", + "microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.analyzers.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Relational/10.0.3": { + "sha512": "Pmh60OK9neVr/M0FJwm9hlzm2bD4Kd65SID8E6SP5c90tExNgXwORrlEWl0oGU/ig9ifpNN4PSpIrnHNozlT5w==", + "type": "package", + "path": "microsoft.entityframeworkcore.relational/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.xml", + "microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.relational.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Sqlite/10.0.3": { + "sha512": "pXP9l00ZzsZuS6ajUJFLHi5vt3vVxHaxQXCwRs73tyqNbjPX/4ac114wyfAepMSmUKq0roFFoTZ7h+1K1+iQjQ==", + "type": "package", + "path": "microsoft.entityframeworkcore.sqlite/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/_._", + "microsoft.entityframeworkcore.sqlite.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.sqlite.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Sqlite.Core/10.0.3": { + "sha512": "bUkOkSwPqvhNlsMMg5dA/PR9S4cVehiHEIZklakRH6JZCFsEBNvUz8kBRGK8Hi6mcaPKUAmVZQkN3moZsBYZLA==", + "type": "package", + "path": "microsoft.entityframeworkcore.sqlite.core/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Sqlite.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Sqlite.xml", + "microsoft.entityframeworkcore.sqlite.core.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.sqlite.core.nuspec" + ] + }, + "Microsoft.Extensions.ApiDescription.Server/6.0.5": { + "sha512": "Ckb5EDBUNJdFWyajfXzUIMRkhf52fHZOQuuZg/oiu8y7zDCVwD0iHhew6MnThjHmevanpxL3f5ci2TtHQEN6bw==", + "type": "package", + "path": "microsoft.extensions.apidescription.server/6.0.5", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "build/Microsoft.Extensions.ApiDescription.Server.props", + "build/Microsoft.Extensions.ApiDescription.Server.targets", + "buildMultiTargeting/Microsoft.Extensions.ApiDescription.Server.props", + "buildMultiTargeting/Microsoft.Extensions.ApiDescription.Server.targets", + "microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512", + "microsoft.extensions.apidescription.server.nuspec", + "tools/Newtonsoft.Json.dll", + "tools/dotnet-getdocument.deps.json", + "tools/dotnet-getdocument.dll", + "tools/dotnet-getdocument.runtimeconfig.json", + "tools/net461-x86/GetDocument.Insider.exe", + "tools/net461-x86/GetDocument.Insider.exe.config", + "tools/net461-x86/Microsoft.Win32.Primitives.dll", + "tools/net461-x86/System.AppContext.dll", + "tools/net461-x86/System.Buffers.dll", + "tools/net461-x86/System.Collections.Concurrent.dll", + "tools/net461-x86/System.Collections.NonGeneric.dll", + "tools/net461-x86/System.Collections.Specialized.dll", + "tools/net461-x86/System.Collections.dll", + "tools/net461-x86/System.ComponentModel.EventBasedAsync.dll", + "tools/net461-x86/System.ComponentModel.Primitives.dll", + "tools/net461-x86/System.ComponentModel.TypeConverter.dll", + "tools/net461-x86/System.ComponentModel.dll", + "tools/net461-x86/System.Console.dll", + "tools/net461-x86/System.Data.Common.dll", + "tools/net461-x86/System.Diagnostics.Contracts.dll", + "tools/net461-x86/System.Diagnostics.Debug.dll", + "tools/net461-x86/System.Diagnostics.DiagnosticSource.dll", + "tools/net461-x86/System.Diagnostics.FileVersionInfo.dll", + "tools/net461-x86/System.Diagnostics.Process.dll", + "tools/net461-x86/System.Diagnostics.StackTrace.dll", + "tools/net461-x86/System.Diagnostics.TextWriterTraceListener.dll", + "tools/net461-x86/System.Diagnostics.Tools.dll", + "tools/net461-x86/System.Diagnostics.TraceSource.dll", + "tools/net461-x86/System.Diagnostics.Tracing.dll", + "tools/net461-x86/System.Drawing.Primitives.dll", + "tools/net461-x86/System.Dynamic.Runtime.dll", + "tools/net461-x86/System.Globalization.Calendars.dll", + "tools/net461-x86/System.Globalization.Extensions.dll", + "tools/net461-x86/System.Globalization.dll", + "tools/net461-x86/System.IO.Compression.ZipFile.dll", + "tools/net461-x86/System.IO.Compression.dll", + "tools/net461-x86/System.IO.FileSystem.DriveInfo.dll", + "tools/net461-x86/System.IO.FileSystem.Primitives.dll", + "tools/net461-x86/System.IO.FileSystem.Watcher.dll", + "tools/net461-x86/System.IO.FileSystem.dll", + "tools/net461-x86/System.IO.IsolatedStorage.dll", + "tools/net461-x86/System.IO.MemoryMappedFiles.dll", + "tools/net461-x86/System.IO.Pipes.dll", + "tools/net461-x86/System.IO.UnmanagedMemoryStream.dll", + "tools/net461-x86/System.IO.dll", + "tools/net461-x86/System.Linq.Expressions.dll", + "tools/net461-x86/System.Linq.Parallel.dll", + "tools/net461-x86/System.Linq.Queryable.dll", + "tools/net461-x86/System.Linq.dll", + "tools/net461-x86/System.Memory.dll", + "tools/net461-x86/System.Net.Http.dll", + "tools/net461-x86/System.Net.NameResolution.dll", + "tools/net461-x86/System.Net.NetworkInformation.dll", + "tools/net461-x86/System.Net.Ping.dll", + "tools/net461-x86/System.Net.Primitives.dll", + "tools/net461-x86/System.Net.Requests.dll", + "tools/net461-x86/System.Net.Security.dll", + "tools/net461-x86/System.Net.Sockets.dll", + "tools/net461-x86/System.Net.WebHeaderCollection.dll", + "tools/net461-x86/System.Net.WebSockets.Client.dll", + "tools/net461-x86/System.Net.WebSockets.dll", + "tools/net461-x86/System.Numerics.Vectors.dll", + "tools/net461-x86/System.ObjectModel.dll", + "tools/net461-x86/System.Reflection.Extensions.dll", + "tools/net461-x86/System.Reflection.Primitives.dll", + "tools/net461-x86/System.Reflection.dll", + "tools/net461-x86/System.Resources.Reader.dll", + "tools/net461-x86/System.Resources.ResourceManager.dll", + "tools/net461-x86/System.Resources.Writer.dll", + "tools/net461-x86/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net461-x86/System.Runtime.CompilerServices.VisualC.dll", + "tools/net461-x86/System.Runtime.Extensions.dll", + "tools/net461-x86/System.Runtime.Handles.dll", + "tools/net461-x86/System.Runtime.InteropServices.RuntimeInformation.dll", + "tools/net461-x86/System.Runtime.InteropServices.dll", + "tools/net461-x86/System.Runtime.Numerics.dll", + "tools/net461-x86/System.Runtime.Serialization.Formatters.dll", + "tools/net461-x86/System.Runtime.Serialization.Json.dll", + "tools/net461-x86/System.Runtime.Serialization.Primitives.dll", + "tools/net461-x86/System.Runtime.Serialization.Xml.dll", + "tools/net461-x86/System.Runtime.dll", + "tools/net461-x86/System.Security.Claims.dll", + "tools/net461-x86/System.Security.Cryptography.Algorithms.dll", + "tools/net461-x86/System.Security.Cryptography.Csp.dll", + "tools/net461-x86/System.Security.Cryptography.Encoding.dll", + "tools/net461-x86/System.Security.Cryptography.Primitives.dll", + "tools/net461-x86/System.Security.Cryptography.X509Certificates.dll", + "tools/net461-x86/System.Security.Principal.dll", + "tools/net461-x86/System.Security.SecureString.dll", + "tools/net461-x86/System.Text.Encoding.Extensions.dll", + "tools/net461-x86/System.Text.Encoding.dll", + "tools/net461-x86/System.Text.RegularExpressions.dll", + "tools/net461-x86/System.Threading.Overlapped.dll", + "tools/net461-x86/System.Threading.Tasks.Parallel.dll", + "tools/net461-x86/System.Threading.Tasks.dll", + "tools/net461-x86/System.Threading.Thread.dll", + "tools/net461-x86/System.Threading.ThreadPool.dll", + "tools/net461-x86/System.Threading.Timer.dll", + "tools/net461-x86/System.Threading.dll", + "tools/net461-x86/System.ValueTuple.dll", + "tools/net461-x86/System.Xml.ReaderWriter.dll", + "tools/net461-x86/System.Xml.XDocument.dll", + "tools/net461-x86/System.Xml.XPath.XDocument.dll", + "tools/net461-x86/System.Xml.XPath.dll", + "tools/net461-x86/System.Xml.XmlDocument.dll", + "tools/net461-x86/System.Xml.XmlSerializer.dll", + "tools/net461-x86/netstandard.dll", + "tools/net461/GetDocument.Insider.exe", + "tools/net461/GetDocument.Insider.exe.config", + "tools/net461/Microsoft.Win32.Primitives.dll", + "tools/net461/System.AppContext.dll", + "tools/net461/System.Buffers.dll", + "tools/net461/System.Collections.Concurrent.dll", + "tools/net461/System.Collections.NonGeneric.dll", + "tools/net461/System.Collections.Specialized.dll", + "tools/net461/System.Collections.dll", + "tools/net461/System.ComponentModel.EventBasedAsync.dll", + "tools/net461/System.ComponentModel.Primitives.dll", + "tools/net461/System.ComponentModel.TypeConverter.dll", + "tools/net461/System.ComponentModel.dll", + "tools/net461/System.Console.dll", + "tools/net461/System.Data.Common.dll", + "tools/net461/System.Diagnostics.Contracts.dll", + "tools/net461/System.Diagnostics.Debug.dll", + "tools/net461/System.Diagnostics.DiagnosticSource.dll", + "tools/net461/System.Diagnostics.FileVersionInfo.dll", + "tools/net461/System.Diagnostics.Process.dll", + "tools/net461/System.Diagnostics.StackTrace.dll", + "tools/net461/System.Diagnostics.TextWriterTraceListener.dll", + "tools/net461/System.Diagnostics.Tools.dll", + "tools/net461/System.Diagnostics.TraceSource.dll", + "tools/net461/System.Diagnostics.Tracing.dll", + "tools/net461/System.Drawing.Primitives.dll", + "tools/net461/System.Dynamic.Runtime.dll", + "tools/net461/System.Globalization.Calendars.dll", + "tools/net461/System.Globalization.Extensions.dll", + "tools/net461/System.Globalization.dll", + "tools/net461/System.IO.Compression.ZipFile.dll", + "tools/net461/System.IO.Compression.dll", + "tools/net461/System.IO.FileSystem.DriveInfo.dll", + "tools/net461/System.IO.FileSystem.Primitives.dll", + "tools/net461/System.IO.FileSystem.Watcher.dll", + "tools/net461/System.IO.FileSystem.dll", + "tools/net461/System.IO.IsolatedStorage.dll", + "tools/net461/System.IO.MemoryMappedFiles.dll", + "tools/net461/System.IO.Pipes.dll", + "tools/net461/System.IO.UnmanagedMemoryStream.dll", + "tools/net461/System.IO.dll", + "tools/net461/System.Linq.Expressions.dll", + "tools/net461/System.Linq.Parallel.dll", + "tools/net461/System.Linq.Queryable.dll", + "tools/net461/System.Linq.dll", + "tools/net461/System.Memory.dll", + "tools/net461/System.Net.Http.dll", + "tools/net461/System.Net.NameResolution.dll", + "tools/net461/System.Net.NetworkInformation.dll", + "tools/net461/System.Net.Ping.dll", + "tools/net461/System.Net.Primitives.dll", + "tools/net461/System.Net.Requests.dll", + "tools/net461/System.Net.Security.dll", + "tools/net461/System.Net.Sockets.dll", + "tools/net461/System.Net.WebHeaderCollection.dll", + "tools/net461/System.Net.WebSockets.Client.dll", + "tools/net461/System.Net.WebSockets.dll", + "tools/net461/System.Numerics.Vectors.dll", + "tools/net461/System.ObjectModel.dll", + "tools/net461/System.Reflection.Extensions.dll", + "tools/net461/System.Reflection.Primitives.dll", + "tools/net461/System.Reflection.dll", + "tools/net461/System.Resources.Reader.dll", + "tools/net461/System.Resources.ResourceManager.dll", + "tools/net461/System.Resources.Writer.dll", + "tools/net461/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net461/System.Runtime.CompilerServices.VisualC.dll", + "tools/net461/System.Runtime.Extensions.dll", + "tools/net461/System.Runtime.Handles.dll", + "tools/net461/System.Runtime.InteropServices.RuntimeInformation.dll", + "tools/net461/System.Runtime.InteropServices.dll", + "tools/net461/System.Runtime.Numerics.dll", + "tools/net461/System.Runtime.Serialization.Formatters.dll", + "tools/net461/System.Runtime.Serialization.Json.dll", + "tools/net461/System.Runtime.Serialization.Primitives.dll", + "tools/net461/System.Runtime.Serialization.Xml.dll", + "tools/net461/System.Runtime.dll", + "tools/net461/System.Security.Claims.dll", + "tools/net461/System.Security.Cryptography.Algorithms.dll", + "tools/net461/System.Security.Cryptography.Csp.dll", + "tools/net461/System.Security.Cryptography.Encoding.dll", + "tools/net461/System.Security.Cryptography.Primitives.dll", + "tools/net461/System.Security.Cryptography.X509Certificates.dll", + "tools/net461/System.Security.Principal.dll", + "tools/net461/System.Security.SecureString.dll", + "tools/net461/System.Text.Encoding.Extensions.dll", + "tools/net461/System.Text.Encoding.dll", + "tools/net461/System.Text.RegularExpressions.dll", + "tools/net461/System.Threading.Overlapped.dll", + "tools/net461/System.Threading.Tasks.Parallel.dll", + "tools/net461/System.Threading.Tasks.dll", + "tools/net461/System.Threading.Thread.dll", + "tools/net461/System.Threading.ThreadPool.dll", + "tools/net461/System.Threading.Timer.dll", + "tools/net461/System.Threading.dll", + "tools/net461/System.ValueTuple.dll", + "tools/net461/System.Xml.ReaderWriter.dll", + "tools/net461/System.Xml.XDocument.dll", + "tools/net461/System.Xml.XPath.XDocument.dll", + "tools/net461/System.Xml.XPath.dll", + "tools/net461/System.Xml.XmlDocument.dll", + "tools/net461/System.Xml.XmlSerializer.dll", + "tools/net461/netstandard.dll", + "tools/netcoreapp2.1/GetDocument.Insider.deps.json", + "tools/netcoreapp2.1/GetDocument.Insider.dll", + "tools/netcoreapp2.1/GetDocument.Insider.runtimeconfig.json", + "tools/netcoreapp2.1/System.Diagnostics.DiagnosticSource.dll" + ] + }, + "Microsoft.Extensions.DependencyModel/10.0.3": { + "sha512": "31kRjr1fgdJO1UZ/AsjL2noqwht+juHMQ8c/oh8CEsDhlM2+0zwVZVsZjxSfOFiPtn5+6kRGuvSbLAufAPT0kA==", + "type": "package", + "path": "microsoft.extensions.dependencymodel/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyModel.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyModel.targets", + "lib/net10.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net10.0/Microsoft.Extensions.DependencyModel.xml", + "lib/net462/Microsoft.Extensions.DependencyModel.dll", + "lib/net462/Microsoft.Extensions.DependencyModel.xml", + "lib/net8.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net8.0/Microsoft.Extensions.DependencyModel.xml", + "lib/net9.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net9.0/Microsoft.Extensions.DependencyModel.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyModel.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyModel.xml", + "microsoft.extensions.dependencymodel.10.0.3.nupkg.sha512", + "microsoft.extensions.dependencymodel.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore/10.0.3": { + "sha512": "iS+BIH8VkzR9ox57Jm6YLlYW9VfcaDL5b05f8acl2lK9goVDJJq6qbfuLarW9PDf3Vrxv05SEk8bYQkhZDfVow==", + "type": "package", + "path": "microsoft.extensions.diagnostics.healthchecks.entityframeworkcore/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "lib/net10.0/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.dll", + "lib/net10.0/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.xml", + "microsoft.extensions.diagnostics.healthchecks.entityframeworkcore.10.0.3.nupkg.sha512", + "microsoft.extensions.diagnostics.healthchecks.entityframeworkcore.nuspec" + ] + }, + "Microsoft.OpenApi/1.6.22": { + "sha512": "aBvunmrdu/x+4CaA/UP1Jx4xWGwk4kymhoIRnn2Vp+zi5/KOPQJ9EkSXHRUr01WcGKtYl3Au7XfkPJbU1G2sjQ==", + "type": "package", + "path": "microsoft.openapi/1.6.22", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/netstandard2.0/Microsoft.OpenApi.dll", + "lib/netstandard2.0/Microsoft.OpenApi.pdb", + "lib/netstandard2.0/Microsoft.OpenApi.xml", + "microsoft.openapi.1.6.22.nupkg.sha512", + "microsoft.openapi.nuspec" + ] + }, + "Microsoft.Win32.SystemEvents/9.0.2": { + "sha512": "5BkGZ6mHp2dHydR29sb0fDfAuqkv30AHtTih8wMzvPZysOmBFvHfnkR2w3tsc0pSiIg8ZoKyefJXWy9r3pBh0w==", + "type": "package", + "path": "microsoft.win32.systemevents/9.0.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Win32.SystemEvents.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Win32.SystemEvents.targets", + "lib/net462/Microsoft.Win32.SystemEvents.dll", + "lib/net462/Microsoft.Win32.SystemEvents.xml", + "lib/net8.0/Microsoft.Win32.SystemEvents.dll", + "lib/net8.0/Microsoft.Win32.SystemEvents.xml", + "lib/net9.0/Microsoft.Win32.SystemEvents.dll", + "lib/net9.0/Microsoft.Win32.SystemEvents.xml", + "lib/netstandard2.0/Microsoft.Win32.SystemEvents.dll", + "lib/netstandard2.0/Microsoft.Win32.SystemEvents.xml", + "microsoft.win32.systemevents.9.0.2.nupkg.sha512", + "microsoft.win32.systemevents.nuspec", + "runtimes/win/lib/net8.0/Microsoft.Win32.SystemEvents.dll", + "runtimes/win/lib/net8.0/Microsoft.Win32.SystemEvents.xml", + "runtimes/win/lib/net9.0/Microsoft.Win32.SystemEvents.dll", + "runtimes/win/lib/net9.0/Microsoft.Win32.SystemEvents.xml", + "useSharedDesignerContext.txt" + ] + }, + "Morestachio/5.0.1.631": { + "sha512": "yvP2BgGaHLTjmqVVgQTqBfrFC8hglE/ts6+7XtAKzFVhaCKi24PF16dqipKUtZGFrEdy2ngeOhJL7gxMEo10IA==", + "type": "package", + "path": "morestachio/5.0.1.631", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Morestachio.png", + "lib/net5.0/Morestachio.dll", + "lib/net5.0/Morestachio.pdb", + "lib/net5.0/Morestachio.xml", + "lib/net6.0/Morestachio.dll", + "lib/net6.0/Morestachio.pdb", + "lib/net6.0/Morestachio.xml", + "lib/netstandard2.0/Morestachio.dll", + "lib/netstandard2.0/Morestachio.pdb", + "lib/netstandard2.0/Morestachio.xml", + "lib/netstandard2.1/Morestachio.dll", + "lib/netstandard2.1/Morestachio.pdb", + "lib/netstandard2.1/Morestachio.xml", + "morestachio.5.0.1.631.nupkg.sha512", + "morestachio.nuspec" + ] + }, + "NEbml/1.1.0.5": { + "sha512": "svtqDc+hue9kbnqNN2KkK4om/hDrc7K127cNb5FIYfgKgzo+JNDPXNLp8NioCchHhBO3lxWd4Cp/iiZZ3aoUqg==", + "type": "package", + "path": "nebml/1.1.0.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net461/NEbml.Core.dll", + "lib/net461/NEbml.Core.xml", + "lib/netstandard2.0/NEbml.Core.dll", + "lib/netstandard2.0/NEbml.Core.xml", + "nebml.1.1.0.5.nupkg.sha512", + "nebml.nuspec" + ] + }, + "Newtonsoft.Json/13.0.4": { + "sha512": "pdgNNMai3zv51W5aq268sujXUyx7SNdE2bj1wZcWjAQrKMFZV260lbqYop1d2GM67JI1huLRwxo9ZqnfF/lC6A==", + "type": "package", + "path": "newtonsoft.json/13.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.md", + "README.md", + "lib/net20/Newtonsoft.Json.dll", + "lib/net20/Newtonsoft.Json.xml", + "lib/net35/Newtonsoft.Json.dll", + "lib/net35/Newtonsoft.Json.xml", + "lib/net40/Newtonsoft.Json.dll", + "lib/net40/Newtonsoft.Json.xml", + "lib/net45/Newtonsoft.Json.dll", + "lib/net45/Newtonsoft.Json.xml", + "lib/net6.0/Newtonsoft.Json.dll", + "lib/net6.0/Newtonsoft.Json.xml", + "lib/netstandard1.0/Newtonsoft.Json.dll", + "lib/netstandard1.0/Newtonsoft.Json.xml", + "lib/netstandard1.3/Newtonsoft.Json.dll", + "lib/netstandard1.3/Newtonsoft.Json.xml", + "lib/netstandard2.0/Newtonsoft.Json.dll", + "lib/netstandard2.0/Newtonsoft.Json.xml", + "newtonsoft.json.13.0.4.nupkg.sha512", + "newtonsoft.json.nuspec", + "packageIcon.png" + ] + }, + "PlaylistsNET/1.4.1": { + "sha512": "GmDShQkKK08YJD94rOoWebe3M2QRk6XZkMcPL80ZcRnFs255kifAJt7I9n6mmSlcQc/BRpgiY3jHDIOxIqA/cA==", + "type": "package", + "path": "playlistsnet/1.4.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net48/PlaylistsNET.dll", + "lib/net6.0/PlaylistsNET.dll", + "lib/net8.0/PlaylistsNET.dll", + "lib/netstandard1.4/PlaylistsNET.dll", + "lib/netstandard2.0/PlaylistsNET.dll", + "playlistsnet.1.4.1.nupkg.sha512", + "playlistsnet.nuspec" + ] + }, + "Polly/8.6.5": { + "sha512": "VqtW2ZE/ALvQMAH1cQY3qZ2cF2OXa3oe/HKMdOv6Q02HCoEW0rsFNfcBONXlHBe1TnjWW1vdRxBEkPeq0/2FHA==", + "type": "package", + "path": "polly/8.6.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "lib/net462/Polly.dll", + "lib/net462/Polly.pdb", + "lib/net462/Polly.xml", + "lib/net472/Polly.dll", + "lib/net472/Polly.pdb", + "lib/net472/Polly.xml", + "lib/net6.0/Polly.dll", + "lib/net6.0/Polly.pdb", + "lib/net6.0/Polly.xml", + "lib/netstandard2.0/Polly.dll", + "lib/netstandard2.0/Polly.pdb", + "lib/netstandard2.0/Polly.xml", + "package-icon.png", + "package-readme.md", + "polly.8.6.5.nupkg.sha512", + "polly.nuspec" + ] + }, + "Polly.Core/8.6.5": { + "sha512": "t+sUVrIwvo7UmsgHGgOG9F0GDZSRIm47u2ylH17Gvcv1q5hNEwgD5GoBlFyc0kh/pebmPyrAgvGsR/65ZBaXlg==", + "type": "package", + "path": "polly.core/8.6.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "lib/net462/Polly.Core.dll", + "lib/net462/Polly.Core.pdb", + "lib/net462/Polly.Core.xml", + "lib/net472/Polly.Core.dll", + "lib/net472/Polly.Core.pdb", + "lib/net472/Polly.Core.xml", + "lib/net6.0/Polly.Core.dll", + "lib/net6.0/Polly.Core.pdb", + "lib/net6.0/Polly.Core.xml", + "lib/net8.0/Polly.Core.dll", + "lib/net8.0/Polly.Core.pdb", + "lib/net8.0/Polly.Core.xml", + "lib/netstandard2.0/Polly.Core.dll", + "lib/netstandard2.0/Polly.Core.pdb", + "lib/netstandard2.0/Polly.Core.xml", + "package-icon.png", + "package-readme.md", + "polly.core.8.6.5.nupkg.sha512", + "polly.core.nuspec" + ] + }, + "prometheus-net/8.2.1": { + "sha512": "3wVgdEPOCBF752s2xps5T+VH+c9mJK8S8GKEDg49084P6JZMumTZI5Te6aJ9MQpX0sx7om6JOnBpIi7ZBmmiDQ==", + "type": "package", + "path": "prometheus-net/8.2.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net462/Prometheus.NetStandard.dll", + "lib/net462/Prometheus.NetStandard.xml", + "lib/net6.0/Prometheus.NetStandard.dll", + "lib/net6.0/Prometheus.NetStandard.xml", + "lib/net7.0/Prometheus.NetStandard.dll", + "lib/net7.0/Prometheus.NetStandard.xml", + "lib/netstandard2.0/Prometheus.NetStandard.dll", + "lib/netstandard2.0/Prometheus.NetStandard.xml", + "prometheus-net-logo.png", + "prometheus-net.8.2.1.nupkg.sha512", + "prometheus-net.nuspec" + ] + }, + "prometheus-net.AspNetCore/8.2.1": { + "sha512": "/4TfTvbwIDqpaKTiWvEsjUywiHYF9zZvGZF5sK15avoDsUO/WPQbKsF8TiMaesuphdFQPK2z52P0zk6j26V0rQ==", + "type": "package", + "path": "prometheus-net.aspnetcore/8.2.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net6.0/Prometheus.AspNetCore.dll", + "lib/net6.0/Prometheus.AspNetCore.xml", + "prometheus-net-logo.png", + "prometheus-net.aspnetcore.8.2.1.nupkg.sha512", + "prometheus-net.aspnetcore.nuspec" + ] + }, + "prometheus-net.DotNetRuntime/4.4.1": { + "sha512": "TGVjy3qhl4Gb6x2aJeZIk/e5bM98YnDGBn1uUxFNdw9268VDUTVpI62lt9162BM/ZH8B9hmaTtgbWZN8wqwDPg==", + "type": "package", + "path": "prometheus-net.dotnetruntime/4.4.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net5.0/prometheus-net.DotNetRuntime.dll", + "lib/net5.0/prometheus-net.DotNetRuntime.pdb", + "lib/net5.0/prometheus-net.DotNetRuntime.xml", + "lib/net6.0/prometheus-net.DotNetRuntime.dll", + "lib/net6.0/prometheus-net.DotNetRuntime.pdb", + "lib/net6.0/prometheus-net.DotNetRuntime.xml", + "lib/netcoreapp3.1/prometheus-net.DotNetRuntime.dll", + "lib/netcoreapp3.1/prometheus-net.DotNetRuntime.pdb", + "lib/netcoreapp3.1/prometheus-net.DotNetRuntime.xml", + "lib/netstandard2.1/prometheus-net.DotNetRuntime.dll", + "lib/netstandard2.1/prometheus-net.DotNetRuntime.pdb", + "lib/netstandard2.1/prometheus-net.DotNetRuntime.xml", + "prometheus-net.dotnetruntime.4.4.1.nupkg.sha512", + "prometheus-net.dotnetruntime.nuspec" + ] + }, + "Serilog/4.3.0": { + "sha512": "+cDryFR0GRhsGOnZSKwaDzRRl4MupvJ42FhCE4zhQRVanX0Jpg6WuCBk59OVhVDPmab1bB+nRykAnykYELA9qQ==", + "type": "package", + "path": "serilog/4.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "build/Serilog.targets", + "icon.png", + "lib/net462/Serilog.dll", + "lib/net462/Serilog.xml", + "lib/net471/Serilog.dll", + "lib/net471/Serilog.xml", + "lib/net6.0/Serilog.dll", + "lib/net6.0/Serilog.xml", + "lib/net8.0/Serilog.dll", + "lib/net8.0/Serilog.xml", + "lib/net9.0/Serilog.dll", + "lib/net9.0/Serilog.xml", + "lib/netstandard2.0/Serilog.dll", + "lib/netstandard2.0/Serilog.xml", + "serilog.4.3.0.nupkg.sha512", + "serilog.nuspec" + ] + }, + "Serilog.AspNetCore/10.0.0": { + "sha512": "a/cNa1mY4On1oJlfGG1wAvxjp5g7OEzk/Jf/nm7NF9cWoE7KlZw1GldrifUBWm9oKibHkR7Lg/l5jy3y7ACR8w==", + "type": "package", + "path": "serilog.aspnetcore/10.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "icon.png", + "lib/net10.0/Serilog.AspNetCore.dll", + "lib/net10.0/Serilog.AspNetCore.xml", + "lib/net462/Serilog.AspNetCore.dll", + "lib/net462/Serilog.AspNetCore.xml", + "lib/net8.0/Serilog.AspNetCore.dll", + "lib/net8.0/Serilog.AspNetCore.xml", + "lib/net9.0/Serilog.AspNetCore.dll", + "lib/net9.0/Serilog.AspNetCore.xml", + "lib/netstandard2.0/Serilog.AspNetCore.dll", + "lib/netstandard2.0/Serilog.AspNetCore.xml", + "lib/netstandard2.1/Serilog.AspNetCore.dll", + "lib/netstandard2.1/Serilog.AspNetCore.xml", + "serilog.aspnetcore.10.0.0.nupkg.sha512", + "serilog.aspnetcore.nuspec" + ] + }, + "Serilog.Enrichers.Thread/4.0.0": { + "sha512": "C7BK25a1rhUyr+Tp+1BYcVlBJq7M2VCHlIgnwoIUVJcicM9jYcvQK18+OeHiXw7uLPSjqWxJIp1EfaZ/RGmEwA==", + "type": "package", + "path": "serilog.enrichers.thread/4.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net462/Serilog.Enrichers.Thread.dll", + "lib/net462/Serilog.Enrichers.Thread.xml", + "lib/net471/Serilog.Enrichers.Thread.dll", + "lib/net471/Serilog.Enrichers.Thread.xml", + "lib/net6.0/Serilog.Enrichers.Thread.dll", + "lib/net6.0/Serilog.Enrichers.Thread.xml", + "lib/net8.0/Serilog.Enrichers.Thread.dll", + "lib/net8.0/Serilog.Enrichers.Thread.xml", + "lib/netstandard2.0/Serilog.Enrichers.Thread.dll", + "lib/netstandard2.0/Serilog.Enrichers.Thread.xml", + "serilog-enricher-nuget.png", + "serilog.enrichers.thread.4.0.0.nupkg.sha512", + "serilog.enrichers.thread.nuspec" + ] + }, + "Serilog.Expressions/5.0.0": { + "sha512": "QhZjXtUcA2QfQRA60m+DfyIfidKsQV7HBstbYEDqzJKMbJH/KnKthkkjciRuYrmFE+scWv1JibC5LlXrdtOUmw==", + "type": "package", + "path": "serilog.expressions/5.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "icon.png", + "lib/net462/Serilog.Expressions.dll", + "lib/net462/Serilog.Expressions.xml", + "lib/net471/Serilog.Expressions.dll", + "lib/net471/Serilog.Expressions.xml", + "lib/net6.0/Serilog.Expressions.dll", + "lib/net6.0/Serilog.Expressions.xml", + "lib/net8.0/Serilog.Expressions.dll", + "lib/net8.0/Serilog.Expressions.xml", + "lib/netstandard2.0/Serilog.Expressions.dll", + "lib/netstandard2.0/Serilog.Expressions.xml", + "serilog.expressions.5.0.0.nupkg.sha512", + "serilog.expressions.nuspec" + ] + }, + "Serilog.Extensions.Hosting/10.0.0": { + "sha512": "E7juuIc+gzoGxgzFooFgAV8g9BfiSXNKsUok9NmEpyAXg2odkcPsMa/Yo4axkJRlh0se7mkYQ1GXDaBemR+b6w==", + "type": "package", + "path": "serilog.extensions.hosting/10.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "icon.png", + "lib/net10.0/Serilog.Extensions.Hosting.dll", + "lib/net10.0/Serilog.Extensions.Hosting.xml", + "lib/net462/Serilog.Extensions.Hosting.dll", + "lib/net462/Serilog.Extensions.Hosting.xml", + "lib/net8.0/Serilog.Extensions.Hosting.dll", + "lib/net8.0/Serilog.Extensions.Hosting.xml", + "lib/net9.0/Serilog.Extensions.Hosting.dll", + "lib/net9.0/Serilog.Extensions.Hosting.xml", + "lib/netstandard2.0/Serilog.Extensions.Hosting.dll", + "lib/netstandard2.0/Serilog.Extensions.Hosting.xml", + "lib/netstandard2.1/Serilog.Extensions.Hosting.dll", + "lib/netstandard2.1/Serilog.Extensions.Hosting.xml", + "serilog.extensions.hosting.10.0.0.nupkg.sha512", + "serilog.extensions.hosting.nuspec" + ] + }, + "Serilog.Extensions.Logging/10.0.0": { + "sha512": "vx0kABKl2dWbBhhqAfTOk53/i8aV/5VaT3a6il9gn72Wqs2pM7EK2OB6No6xdqK2IaY6Zf9gdjLuK9BVa2rT+Q==", + "type": "package", + "path": "serilog.extensions.logging/10.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net10.0/Serilog.Extensions.Logging.dll", + "lib/net10.0/Serilog.Extensions.Logging.xml", + "lib/net462/Serilog.Extensions.Logging.dll", + "lib/net462/Serilog.Extensions.Logging.xml", + "lib/net8.0/Serilog.Extensions.Logging.dll", + "lib/net8.0/Serilog.Extensions.Logging.xml", + "lib/net9.0/Serilog.Extensions.Logging.dll", + "lib/net9.0/Serilog.Extensions.Logging.xml", + "lib/netstandard2.0/Serilog.Extensions.Logging.dll", + "lib/netstandard2.0/Serilog.Extensions.Logging.xml", + "lib/netstandard2.1/Serilog.Extensions.Logging.dll", + "lib/netstandard2.1/Serilog.Extensions.Logging.xml", + "serilog-extension-nuget.png", + "serilog.extensions.logging.10.0.0.nupkg.sha512", + "serilog.extensions.logging.nuspec" + ] + }, + "Serilog.Formatting.Compact/3.0.0": { + "sha512": "wQsv14w9cqlfB5FX2MZpNsTawckN4a8dryuNGbebB/3Nh1pXnROHZov3swtu3Nj5oNG7Ba+xdu7Et/ulAUPanQ==", + "type": "package", + "path": "serilog.formatting.compact/3.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net462/Serilog.Formatting.Compact.dll", + "lib/net462/Serilog.Formatting.Compact.xml", + "lib/net471/Serilog.Formatting.Compact.dll", + "lib/net471/Serilog.Formatting.Compact.xml", + "lib/net6.0/Serilog.Formatting.Compact.dll", + "lib/net6.0/Serilog.Formatting.Compact.xml", + "lib/net8.0/Serilog.Formatting.Compact.dll", + "lib/net8.0/Serilog.Formatting.Compact.xml", + "lib/netstandard2.0/Serilog.Formatting.Compact.dll", + "lib/netstandard2.0/Serilog.Formatting.Compact.xml", + "serilog-extension-nuget.png", + "serilog.formatting.compact.3.0.0.nupkg.sha512", + "serilog.formatting.compact.nuspec" + ] + }, + "Serilog.Settings.Configuration/10.0.0": { + "sha512": "LNq+ibS1sbhTqPV1FIE69/9AJJbfaOhnaqkzcjFy95o+4U+STsta9mi97f1smgXsWYKICDeGUf8xUGzd/52/uA==", + "type": "package", + "path": "serilog.settings.configuration/10.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "icon.png", + "lib/net10.0/Serilog.Settings.Configuration.dll", + "lib/net10.0/Serilog.Settings.Configuration.xml", + "lib/net462/Serilog.Settings.Configuration.dll", + "lib/net462/Serilog.Settings.Configuration.xml", + "lib/net8.0/Serilog.Settings.Configuration.dll", + "lib/net8.0/Serilog.Settings.Configuration.xml", + "lib/net9.0/Serilog.Settings.Configuration.dll", + "lib/net9.0/Serilog.Settings.Configuration.xml", + "lib/netstandard2.0/Serilog.Settings.Configuration.dll", + "lib/netstandard2.0/Serilog.Settings.Configuration.xml", + "serilog.settings.configuration.10.0.0.nupkg.sha512", + "serilog.settings.configuration.nuspec" + ] + }, + "Serilog.Sinks.Async/2.1.0": { + "sha512": "SnmRknWsSMgyo9wDXeZZCqSp48kkQYy44taSM6vcpxfiRICzSf09oLKEmVr0RCwQnfd8mJQ2WNN6nvhqf0RowQ==", + "type": "package", + "path": "serilog.sinks.async/2.1.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net462/Serilog.Sinks.Async.dll", + "lib/net462/Serilog.Sinks.Async.xml", + "lib/net471/Serilog.Sinks.Async.dll", + "lib/net471/Serilog.Sinks.Async.xml", + "lib/net6.0/Serilog.Sinks.Async.dll", + "lib/net6.0/Serilog.Sinks.Async.xml", + "lib/net8.0/Serilog.Sinks.Async.dll", + "lib/net8.0/Serilog.Sinks.Async.xml", + "lib/netstandard2.0/Serilog.Sinks.Async.dll", + "lib/netstandard2.0/Serilog.Sinks.Async.xml", + "serilog-sink-nuget.png", + "serilog.sinks.async.2.1.0.nupkg.sha512", + "serilog.sinks.async.nuspec" + ] + }, + "Serilog.Sinks.Console/6.1.1": { + "sha512": "8jbqgjUyZlfCuSTaJk6lOca465OndqOz3KZP6Cryt/IqZYybyBu7GP0fE/AXBzrrQB3EBmQntBFAvMVz1COvAA==", + "type": "package", + "path": "serilog.sinks.console/6.1.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "icon.png", + "lib/net462/Serilog.Sinks.Console.dll", + "lib/net462/Serilog.Sinks.Console.xml", + "lib/net471/Serilog.Sinks.Console.dll", + "lib/net471/Serilog.Sinks.Console.xml", + "lib/net6.0/Serilog.Sinks.Console.dll", + "lib/net6.0/Serilog.Sinks.Console.xml", + "lib/net8.0/Serilog.Sinks.Console.dll", + "lib/net8.0/Serilog.Sinks.Console.xml", + "lib/netstandard2.0/Serilog.Sinks.Console.dll", + "lib/netstandard2.0/Serilog.Sinks.Console.xml", + "serilog.sinks.console.6.1.1.nupkg.sha512", + "serilog.sinks.console.nuspec" + ] + }, + "Serilog.Sinks.Debug/3.0.0": { + "sha512": "4BzXcdrgRX7wde9PmHuYd9U6YqycCC28hhpKonK7hx0wb19eiuRj16fPcPSVp0o/Y1ipJuNLYQ00R3q2Zs8FDA==", + "type": "package", + "path": "serilog.sinks.debug/3.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "icon.png", + "lib/net462/Serilog.Sinks.Debug.dll", + "lib/net462/Serilog.Sinks.Debug.xml", + "lib/net471/Serilog.Sinks.Debug.dll", + "lib/net471/Serilog.Sinks.Debug.xml", + "lib/net6.0/Serilog.Sinks.Debug.dll", + "lib/net6.0/Serilog.Sinks.Debug.xml", + "lib/net8.0/Serilog.Sinks.Debug.dll", + "lib/net8.0/Serilog.Sinks.Debug.xml", + "lib/netstandard2.0/Serilog.Sinks.Debug.dll", + "lib/netstandard2.0/Serilog.Sinks.Debug.xml", + "serilog.sinks.debug.3.0.0.nupkg.sha512", + "serilog.sinks.debug.nuspec" + ] + }, + "Serilog.Sinks.File/7.0.0": { + "sha512": "fKL7mXv7qaiNBUC71ssvn/dU0k9t0o45+qm2XgKAlSt19xF+ijjxyA3R6HmCgfKEKwfcfkwWjayuQtRueZFkYw==", + "type": "package", + "path": "serilog.sinks.file/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net462/Serilog.Sinks.File.dll", + "lib/net462/Serilog.Sinks.File.xml", + "lib/net471/Serilog.Sinks.File.dll", + "lib/net471/Serilog.Sinks.File.xml", + "lib/net6.0/Serilog.Sinks.File.dll", + "lib/net6.0/Serilog.Sinks.File.xml", + "lib/net8.0/Serilog.Sinks.File.dll", + "lib/net8.0/Serilog.Sinks.File.xml", + "lib/net9.0/Serilog.Sinks.File.dll", + "lib/net9.0/Serilog.Sinks.File.xml", + "lib/netstandard2.0/Serilog.Sinks.File.dll", + "lib/netstandard2.0/Serilog.Sinks.File.xml", + "serilog-sink-nuget.png", + "serilog.sinks.file.7.0.0.nupkg.sha512", + "serilog.sinks.file.nuspec" + ] + }, + "Serilog.Sinks.Graylog/3.1.1": { + "sha512": "9qgpkXGtRdv3kH7adSEe3k4MiAWNaSgDc+/1vfIkJiPospuI+YXYErBDECvPJ/0JVnspuXQnxUCQHpDRP5hgCA==", + "type": "package", + "path": "serilog.sinks.graylog/3.1.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net6.0/Serilog.Sinks.Graylog.Core.dll", + "lib/net6.0/Serilog.Sinks.Graylog.dll", + "lib/net7.0/Serilog.Sinks.Graylog.Core.dll", + "lib/net7.0/Serilog.Sinks.Graylog.dll", + "lib/netstandard2.0/Serilog.Sinks.Graylog.Core.dll", + "lib/netstandard2.0/Serilog.Sinks.Graylog.dll", + "serilog-sink-nuget.png", + "serilog.sinks.graylog.3.1.1.nupkg.sha512", + "serilog.sinks.graylog.nuspec" + ] + }, + "SerilogAnalyzer/0.15.0": { + "sha512": "sVpwfls4MfNnwIXLSGCgaUnV+c9kgJ8ia6GsyRcpd4Vs3gLogSDtSYBYrre2K2u/PNMo8GgG09RehwVnze70Tw==", + "type": "package", + "path": "seriloganalyzer/0.15.0", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/SerilogAnalyzer.dll", + "seriloganalyzer.0.15.0.nupkg.sha512", + "seriloganalyzer.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "ShimSkiaSharp/3.4.1": { + "sha512": "ab3J5OGdwYLyXnom90OghS9NyRPH9dtsRtW5B3mL+F43YTlxido3nUtiR9NjB/sI3jvG/J744C29bsSBQ28AfQ==", + "type": "package", + "path": "shimskiasharp/3.4.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net10.0/ShimSkiaSharp.dll", + "lib/net461/ShimSkiaSharp.dll", + "lib/net6.0/ShimSkiaSharp.dll", + "lib/net8.0/ShimSkiaSharp.dll", + "lib/netstandard2.0/ShimSkiaSharp.dll", + "shimskiasharp.3.4.1.nupkg.sha512", + "shimskiasharp.nuspec" + ] + }, + "SkiaSharp/3.116.1": { + "sha512": "DNDwbRjP+aMo27dV2h/uHCVTcWubWWxHnPLiePNyl24f4Pv43mQ8AQQeseOrKR+J3AOCEs6t0sUjo0aa3j3RWQ==", + "type": "package", + "path": "skiasharp/3.116.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "README.md", + "icon.png", + "interactive-extensions/dotnet/SkiaSharp.DotNet.Interactive.dll", + "lib/net462/SkiaSharp.dll", + "lib/net462/SkiaSharp.pdb", + "lib/net6.0/SkiaSharp.dll", + "lib/net6.0/SkiaSharp.pdb", + "lib/net8.0-android34.0/SkiaSharp.dll", + "lib/net8.0-android34.0/SkiaSharp.pdb", + "lib/net8.0-android34.0/SkiaSharp.xml", + "lib/net8.0-ios17.0/SkiaSharp.dll", + "lib/net8.0-ios17.0/SkiaSharp.pdb", + "lib/net8.0-maccatalyst17.0/SkiaSharp.dll", + "lib/net8.0-maccatalyst17.0/SkiaSharp.pdb", + "lib/net8.0-macos14.0/SkiaSharp.dll", + "lib/net8.0-macos14.0/SkiaSharp.pdb", + "lib/net8.0-tizen7.0/SkiaSharp.dll", + "lib/net8.0-tizen7.0/SkiaSharp.pdb", + "lib/net8.0-tvos17.0/SkiaSharp.dll", + "lib/net8.0-tvos17.0/SkiaSharp.pdb", + "lib/net8.0-windows10.0.19041/SkiaSharp.dll", + "lib/net8.0-windows10.0.19041/SkiaSharp.pdb", + "lib/net8.0/SkiaSharp.dll", + "lib/net8.0/SkiaSharp.pdb", + "lib/netstandard2.0/SkiaSharp.dll", + "lib/netstandard2.0/SkiaSharp.pdb", + "lib/netstandard2.1/SkiaSharp.dll", + "lib/netstandard2.1/SkiaSharp.pdb", + "ref/net462/SkiaSharp.dll", + "ref/net6.0/SkiaSharp.dll", + "ref/net8.0-android34.0/SkiaSharp.dll", + "ref/net8.0-ios17.0/SkiaSharp.dll", + "ref/net8.0-maccatalyst17.0/SkiaSharp.dll", + "ref/net8.0-macos14.0/SkiaSharp.dll", + "ref/net8.0-tizen7.0/SkiaSharp.dll", + "ref/net8.0-tvos17.0/SkiaSharp.dll", + "ref/net8.0-windows10.0.19041/SkiaSharp.dll", + "ref/net8.0/SkiaSharp.dll", + "ref/netstandard2.0/SkiaSharp.dll", + "ref/netstandard2.1/SkiaSharp.dll", + "skiasharp.3.116.1.nupkg.sha512", + "skiasharp.nuspec" + ] + }, + "SkiaSharp.HarfBuzz/3.116.1": { + "sha512": "ibDG1+quN86vBd9ztjDAC9wnvS1nRZ6ydTUOSod4NsRHWdLLGzWYn1IOF4Cg9iJh5cQHdpzhUZBQE0JMKznrow==", + "type": "package", + "path": "skiasharp.harfbuzz/3.116.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "README.md", + "icon.png", + "lib/net462/SkiaSharp.HarfBuzz.dll", + "lib/net462/SkiaSharp.HarfBuzz.pdb", + "lib/net6.0/SkiaSharp.HarfBuzz.dll", + "lib/net6.0/SkiaSharp.HarfBuzz.pdb", + "lib/net8.0/SkiaSharp.HarfBuzz.dll", + "lib/net8.0/SkiaSharp.HarfBuzz.pdb", + "lib/netstandard2.0/SkiaSharp.HarfBuzz.dll", + "lib/netstandard2.0/SkiaSharp.HarfBuzz.pdb", + "lib/netstandard2.1/SkiaSharp.HarfBuzz.dll", + "lib/netstandard2.1/SkiaSharp.HarfBuzz.pdb", + "skiasharp.harfbuzz.3.116.1.nupkg.sha512", + "skiasharp.harfbuzz.nuspec" + ] + }, + "SkiaSharp.NativeAssets.Linux/3.116.1": { + "sha512": "f4OSsrMwe+Ei9KpNc3EMUiMcZkQSYepHCaDuvMyLBaHVQd+9Ywq/rlbmapqztsU6k2iPEVLD8ZuBJm9RDJsAow==", + "type": "package", + "path": "skiasharp.nativeassets.linux/3.116.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "README.md", + "THIRD-PARTY-NOTICES.txt", + "buildTransitive/net462/SkiaSharp.NativeAssets.Linux.targets", + "icon.png", + "lib/net462/_._", + "lib/net6.0/_._", + "lib/net8.0/_._", + "lib/netstandard2.0/_._", + "lib/netstandard2.1/_._", + "runtimes/linux-arm/native/libSkiaSharp.so", + "runtimes/linux-arm/native/libSkiaSharp.so.116.0.0", + "runtimes/linux-arm64/native/libSkiaSharp.so", + "runtimes/linux-arm64/native/libSkiaSharp.so.116.0.0", + "runtimes/linux-musl-x64/native/libSkiaSharp.so", + "runtimes/linux-musl-x64/native/libSkiaSharp.so.116.0.0", + "runtimes/linux-x64/native/libSkiaSharp.so", + "runtimes/linux-x64/native/libSkiaSharp.so.116.0.0", + "skiasharp.nativeassets.linux.3.116.1.nupkg.sha512", + "skiasharp.nativeassets.linux.nuspec" + ] + }, + "SkiaSharp.NativeAssets.macOS/3.116.1": { + "sha512": "3KPvpKysDmEMt0NnAZPX5U6KFk0LmG/72/IjAIJemIksIZ0Tjs9pGpr3L+zboVCv1MLVoJLKl3nJDXUG6Jda6A==", + "type": "package", + "path": "skiasharp.nativeassets.macos/3.116.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "README.md", + "THIRD-PARTY-NOTICES.txt", + "buildTransitive/net462/SkiaSharp.NativeAssets.macOS.targets", + "buildTransitive/net8.0-macos14.0/SkiaSharp.NativeAssets.macOS.targets", + "icon.png", + "lib/net462/_._", + "lib/net6.0/_._", + "lib/net8.0-macos14.0/_._", + "lib/net8.0/_._", + "lib/netstandard2.0/_._", + "lib/netstandard2.1/_._", + "runtimes/osx/native/libSkiaSharp.dylib", + "skiasharp.nativeassets.macos.3.116.1.nupkg.sha512", + "skiasharp.nativeassets.macos.nuspec" + ] + }, + "SkiaSharp.NativeAssets.Win32/3.116.1": { + "sha512": "dRQ75MCI8oz6zAs2Y1w6pq6ARs4MhdNG+gf3doOxOxdnueDXffQLGQIxON54GDoxc0WjKOoHMKBR4DhaduwwQw==", + "type": "package", + "path": "skiasharp.nativeassets.win32/3.116.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "README.md", + "THIRD-PARTY-NOTICES.txt", + "buildTransitive/net462/SkiaSharp.NativeAssets.Win32.targets", + "icon.png", + "lib/net462/_._", + "lib/net6.0-windows10.0.19041/_._", + "lib/net6.0/_._", + "lib/net8.0-windows10.0.19041/_._", + "lib/net8.0/_._", + "lib/netstandard2.0/_._", + "lib/netstandard2.1/_._", + "runtimes/win-arm64/native/libSkiaSharp.dll", + "runtimes/win-x64/native/libSkiaSharp.dll", + "runtimes/win-x86/native/libSkiaSharp.dll", + "skiasharp.nativeassets.win32.3.116.1.nupkg.sha512", + "skiasharp.nativeassets.win32.nuspec" + ] + }, + "SmartAnalyzers.MultithreadingAnalyzer/1.1.31": { + "sha512": "2f2k7bbhDd132ArglCKpzKoWBcp3uzbIFcb4aosnlqIKlfYKDE2HevBVRNVa+LkWFnjXFFWs47Bo96fu8iS//Q==", + "type": "package", + "path": "smartanalyzers.multithreadinganalyzer/1.1.31", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/SmartAnalyzers.MultithreadingAnalyzer.dll", + "smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512", + "smartanalyzers.multithreadinganalyzer.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "SQLitePCLRaw.bundle_e_sqlite3/2.1.11": { + "sha512": "DC4nA7yWnf4UZdgJDF+9Mus4/cb0Y3Sfgi3gDnAoKNAIBwzkskNAbNbyu+u4atT0ruVlZNJfwZmwiEwE5oz9LQ==", + "type": "package", + "path": "sqlitepclraw.bundle_e_sqlite3/2.1.11", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/monoandroid90/SQLitePCLRaw.batteries_v2.dll", + "lib/net461/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-android31.0/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-android31.0/SQLitePCLRaw.batteries_v2.xml", + "lib/net6.0-ios14.0/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-ios14.2/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-tvos10.0/SQLitePCLRaw.batteries_v2.dll", + "lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll", + "lib/xamarinios10/SQLitePCLRaw.batteries_v2.dll", + "sqlitepclraw.bundle_e_sqlite3.2.1.11.nupkg.sha512", + "sqlitepclraw.bundle_e_sqlite3.nuspec" + ] + }, + "SQLitePCLRaw.core/2.1.11": { + "sha512": "PK0GLFkfhZzLQeR3PJf71FmhtHox+U3vcY6ZtswoMjrefkB9k6ErNJEnwXqc5KgXDSjige2XXrezqS39gkpQKA==", + "type": "package", + "path": "sqlitepclraw.core/2.1.11", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/SQLitePCLRaw.core.dll", + "sqlitepclraw.core.2.1.11.nupkg.sha512", + "sqlitepclraw.core.nuspec" + ] + }, + "SQLitePCLRaw.lib.e_sqlite3/2.1.11": { + "sha512": "Ev2ytaXiOlWZ4b3R67GZBsemTINslLD1DCJr2xiacpn4tbapu0Q4dHEzSvZSMnVWeE5nlObU3VZN2p81q3XOYQ==", + "type": "package", + "path": "sqlitepclraw.lib.e_sqlite3/2.1.11", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "buildTransitive/net461/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net6.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net7.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net8.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net9.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "lib/net461/_._", + "lib/netstandard2.0/_._", + "runtimes/browser-wasm/nativeassets/net6.0/e_sqlite3.a", + "runtimes/browser-wasm/nativeassets/net7.0/e_sqlite3.a", + "runtimes/browser-wasm/nativeassets/net8.0/e_sqlite3.a", + "runtimes/browser-wasm/nativeassets/net9.0/e_sqlite3.a", + "runtimes/linux-arm/native/libe_sqlite3.so", + "runtimes/linux-arm64/native/libe_sqlite3.so", + "runtimes/linux-armel/native/libe_sqlite3.so", + "runtimes/linux-mips64/native/libe_sqlite3.so", + "runtimes/linux-musl-arm/native/libe_sqlite3.so", + "runtimes/linux-musl-arm64/native/libe_sqlite3.so", + "runtimes/linux-musl-riscv64/native/libe_sqlite3.so", + "runtimes/linux-musl-s390x/native/libe_sqlite3.so", + "runtimes/linux-musl-x64/native/libe_sqlite3.so", + "runtimes/linux-ppc64le/native/libe_sqlite3.so", + "runtimes/linux-riscv64/native/libe_sqlite3.so", + "runtimes/linux-s390x/native/libe_sqlite3.so", + "runtimes/linux-x64/native/libe_sqlite3.so", + "runtimes/linux-x86/native/libe_sqlite3.so", + "runtimes/maccatalyst-arm64/native/libe_sqlite3.dylib", + "runtimes/maccatalyst-x64/native/libe_sqlite3.dylib", + "runtimes/osx-arm64/native/libe_sqlite3.dylib", + "runtimes/osx-x64/native/libe_sqlite3.dylib", + "runtimes/win-arm/native/e_sqlite3.dll", + "runtimes/win-arm64/native/e_sqlite3.dll", + "runtimes/win-x64/native/e_sqlite3.dll", + "runtimes/win-x86/native/e_sqlite3.dll", + "runtimes/win10-arm/nativeassets/uap10.0/e_sqlite3.dll", + "runtimes/win10-arm64/nativeassets/uap10.0/e_sqlite3.dll", + "runtimes/win10-x64/nativeassets/uap10.0/e_sqlite3.dll", + "runtimes/win10-x86/nativeassets/uap10.0/e_sqlite3.dll", + "sqlitepclraw.lib.e_sqlite3.2.1.11.nupkg.sha512", + "sqlitepclraw.lib.e_sqlite3.nuspec" + ] + }, + "SQLitePCLRaw.provider.e_sqlite3/2.1.11": { + "sha512": "Y/0ZkR+r0Cg3DQFuCl1RBnv/tmxpIZRU3HUvelPw6MVaKHwYYR8YNvgs0vuNuXCMvlyJ+Fh88U1D4tah1tt6qw==", + "type": "package", + "path": "sqlitepclraw.provider.e_sqlite3/2.1.11", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net6.0-windows7.0/SQLitePCLRaw.provider.e_sqlite3.dll", + "lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll", + "lib/netstandard2.0/SQLitePCLRaw.provider.e_sqlite3.dll", + "sqlitepclraw.provider.e_sqlite3.2.1.11.nupkg.sha512", + "sqlitepclraw.provider.e_sqlite3.nuspec" + ] + }, + "StyleCop.Analyzers/1.2.0-beta.556": { + "sha512": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", + "type": "package", + "path": "stylecop.analyzers/1.2.0-beta.556", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "THIRD-PARTY-NOTICES.txt", + "stylecop.analyzers.1.2.0-beta.556.nupkg.sha512", + "stylecop.analyzers.nuspec" + ] + }, + "StyleCop.Analyzers.Unstable/1.2.0.556": { + "sha512": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==", + "type": "package", + "path": "stylecop.analyzers.unstable/1.2.0.556", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "THIRD-PARTY-NOTICES.txt", + "analyzers/dotnet/cs/StyleCop.Analyzers.CodeFixes.dll", + "analyzers/dotnet/cs/StyleCop.Analyzers.dll", + "analyzers/dotnet/cs/de-DE/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/en-GB/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/es-MX/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/fr-FR/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/pl-PL/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/pt-BR/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/ru-RU/StyleCop.Analyzers.resources.dll", + "rulesets/StyleCopAnalyzersDefault.ruleset", + "stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512", + "stylecop.analyzers.unstable.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Svg.Custom/3.4.1": { + "sha512": "ksHa7Zv8X1InxSWAeM0sSSvZM2W6FWgneXjU0bdYrQ20Q6q0nl3g0uaSWe53i/CC1OmR+/JAxsp8E3/wigtcng==", + "type": "package", + "path": "svg.custom/3.4.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net10.0/Svg.Custom.dll", + "lib/net6.0/Svg.Custom.dll", + "lib/net8.0/Svg.Custom.dll", + "lib/netstandard2.0/Svg.Custom.dll", + "svg.custom.3.4.1.nupkg.sha512", + "svg.custom.nuspec" + ] + }, + "Svg.Model/3.4.1": { + "sha512": "Zn04CWWIwV+qlJ4x4vg8FdCYNew5dRcsvkcCpzZggFKHGExIn4WTBtJMuLH8htuiLnbbgF6woIYpUll+seHfjw==", + "type": "package", + "path": "svg.model/3.4.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net10.0/Svg.Model.dll", + "lib/net461/Svg.Model.dll", + "lib/net6.0/Svg.Model.dll", + "lib/net8.0/Svg.Model.dll", + "lib/netstandard2.0/Svg.Model.dll", + "svg.model.3.4.1.nupkg.sha512", + "svg.model.nuspec" + ] + }, + "Svg.Skia/3.4.1": { + "sha512": "fNGHIeIUtEDo41P4MYfVqjNL902t8EP5/tBY9vYDg8VbKSOD84ZlBfCL4KSxjf//HcJi1Uz4uZCUXuEAKJirCw==", + "type": "package", + "path": "svg.skia/3.4.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net10.0/Svg.Skia.dll", + "lib/net461/Svg.Skia.dll", + "lib/net6.0/Svg.Skia.dll", + "lib/net8.0/Svg.Skia.dll", + "lib/netstandard2.0/Svg.Skia.dll", + "svg.skia.3.4.1.nupkg.sha512", + "svg.skia.nuspec" + ] + }, + "Swashbuckle.AspNetCore/7.3.2": { + "sha512": "WEkW7Cl8bexT75eURuqrqM/TQx+ftWKOWlCHzmw3Fl3oAmxiBO/lDVOuuqZsaBCHQIBcZxA6BbyiG5dv2mD2tw==", + "type": "package", + "path": "swashbuckle.aspnetcore/7.3.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/Swashbuckle.AspNetCore.props", + "buildMultiTargeting/Swashbuckle.AspNetCore.props", + "docs/package-readme.md", + "swashbuckle.aspnetcore.7.3.2.nupkg.sha512", + "swashbuckle.aspnetcore.nuspec" + ] + }, + "Swashbuckle.AspNetCore.ReDoc/6.9.0": { + "sha512": "E/KCx9LRDgWD7vr+BHyLdC2dZMuLhwOMp4xjuJOJChoeWQYVpnOYRdQngeZxGFYGxiahEJ2B1fCvL3XTvzArOQ==", + "type": "package", + "path": "swashbuckle.aspnetcore.redoc/6.9.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net5.0/Swashbuckle.AspNetCore.ReDoc.dll", + "lib/net5.0/Swashbuckle.AspNetCore.ReDoc.pdb", + "lib/net5.0/Swashbuckle.AspNetCore.ReDoc.xml", + "lib/net6.0/Swashbuckle.AspNetCore.ReDoc.dll", + "lib/net6.0/Swashbuckle.AspNetCore.ReDoc.pdb", + "lib/net6.0/Swashbuckle.AspNetCore.ReDoc.xml", + "lib/net7.0/Swashbuckle.AspNetCore.ReDoc.dll", + "lib/net7.0/Swashbuckle.AspNetCore.ReDoc.pdb", + "lib/net7.0/Swashbuckle.AspNetCore.ReDoc.xml", + "lib/net8.0/Swashbuckle.AspNetCore.ReDoc.dll", + "lib/net8.0/Swashbuckle.AspNetCore.ReDoc.pdb", + "lib/net8.0/Swashbuckle.AspNetCore.ReDoc.xml", + "lib/netcoreapp3.0/Swashbuckle.AspNetCore.ReDoc.dll", + "lib/netcoreapp3.0/Swashbuckle.AspNetCore.ReDoc.pdb", + "lib/netcoreapp3.0/Swashbuckle.AspNetCore.ReDoc.xml", + "lib/netstandard2.0/Swashbuckle.AspNetCore.ReDoc.dll", + "lib/netstandard2.0/Swashbuckle.AspNetCore.ReDoc.pdb", + "lib/netstandard2.0/Swashbuckle.AspNetCore.ReDoc.xml", + "package-readme.md", + "swashbuckle.aspnetcore.redoc.6.9.0.nupkg.sha512", + "swashbuckle.aspnetcore.redoc.nuspec" + ] + }, + "Swashbuckle.AspNetCore.Swagger/7.3.2": { + "sha512": "39Z2N3cLHDcDinu2IW5IaQO9wGPKTbc+vKbjIT/6gN3jFYypWY5U3O8i7Y9rmYDgxkm6niICnkcFZ3hEvikIHA==", + "type": "package", + "path": "swashbuckle.aspnetcore.swagger/7.3.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net6.0/Swashbuckle.AspNetCore.Swagger.dll", + "lib/net6.0/Swashbuckle.AspNetCore.Swagger.pdb", + "lib/net6.0/Swashbuckle.AspNetCore.Swagger.xml", + "lib/net8.0/Swashbuckle.AspNetCore.Swagger.dll", + "lib/net8.0/Swashbuckle.AspNetCore.Swagger.pdb", + "lib/net8.0/Swashbuckle.AspNetCore.Swagger.xml", + "lib/net9.0/Swashbuckle.AspNetCore.Swagger.dll", + "lib/net9.0/Swashbuckle.AspNetCore.Swagger.pdb", + "lib/net9.0/Swashbuckle.AspNetCore.Swagger.xml", + "lib/netstandard2.0/Swashbuckle.AspNetCore.Swagger.dll", + "lib/netstandard2.0/Swashbuckle.AspNetCore.Swagger.pdb", + "lib/netstandard2.0/Swashbuckle.AspNetCore.Swagger.xml", + "package-readme.md", + "swashbuckle.aspnetcore.swagger.7.3.2.nupkg.sha512", + "swashbuckle.aspnetcore.swagger.nuspec" + ] + }, + "Swashbuckle.AspNetCore.SwaggerGen/7.3.2": { + "sha512": "x5famjpcgazpT/8eEIlpSYSTZDq6ojMARhuJgj6O67Uk+Yb1DdDM5yikdL7GK1pMk5rYBETUCRWYP6rp+vHnlA==", + "type": "package", + "path": "swashbuckle.aspnetcore.swaggergen/7.3.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net6.0/Swashbuckle.AspNetCore.SwaggerGen.dll", + "lib/net6.0/Swashbuckle.AspNetCore.SwaggerGen.pdb", + "lib/net6.0/Swashbuckle.AspNetCore.SwaggerGen.xml", + "lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.dll", + "lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.pdb", + "lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.xml", + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll", + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.pdb", + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.xml", + "lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerGen.dll", + "lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerGen.pdb", + "lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerGen.xml", + "package-readme.md", + "swashbuckle.aspnetcore.swaggergen.7.3.2.nupkg.sha512", + "swashbuckle.aspnetcore.swaggergen.nuspec" + ] + }, + "Swashbuckle.AspNetCore.SwaggerUI/7.3.2": { + "sha512": "SNQuFjYin9KhkoCSVOD5/Y4VEyAjKBDeZkIqqY6x+oI21HMKzzVkeJA9V7tWFXf5df4m/+pTIlbKO7g5W9UroQ==", + "type": "package", + "path": "swashbuckle.aspnetcore.swaggerui/7.3.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net6.0/Swashbuckle.AspNetCore.SwaggerUI.dll", + "lib/net6.0/Swashbuckle.AspNetCore.SwaggerUI.pdb", + "lib/net6.0/Swashbuckle.AspNetCore.SwaggerUI.xml", + "lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.dll", + "lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.pdb", + "lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.xml", + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll", + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.pdb", + "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.xml", + "lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerUI.dll", + "lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerUI.pdb", + "lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerUI.xml", + "package-readme.md", + "swashbuckle.aspnetcore.swaggerui.7.3.2.nupkg.sha512", + "swashbuckle.aspnetcore.swaggerui.nuspec" + ] + }, + "System.Drawing.Common/9.0.2": { + "sha512": "JU947wzf8JbBS16Y5EIZzAlyQU+k68D7LRx6y03s2wlhlvLqkt/8uPBrjv2hJnnaJKbdb0GhQ3JZsfYXhrRjyg==", + "type": "package", + "path": "system.drawing.common/9.0.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Drawing.Common.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/System.Drawing.Common.targets", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net462/System.Drawing.Common.dll", + "lib/net462/System.Drawing.Common.pdb", + "lib/net462/System.Drawing.Common.xml", + "lib/net8.0/System.Drawing.Common.dll", + "lib/net8.0/System.Drawing.Common.pdb", + "lib/net8.0/System.Drawing.Common.xml", + "lib/net8.0/System.Private.Windows.Core.dll", + "lib/net8.0/System.Private.Windows.Core.xml", + "lib/net9.0/System.Drawing.Common.dll", + "lib/net9.0/System.Drawing.Common.pdb", + "lib/net9.0/System.Drawing.Common.xml", + "lib/net9.0/System.Private.Windows.Core.dll", + "lib/net9.0/System.Private.Windows.Core.xml", + "lib/netstandard2.0/System.Drawing.Common.dll", + "lib/netstandard2.0/System.Drawing.Common.pdb", + "lib/netstandard2.0/System.Drawing.Common.xml", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "system.drawing.common.9.0.2.nupkg.sha512", + "system.drawing.common.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "TagLibSharp/2.3.0": { + "sha512": "Qo4z6ZjnIfbR3Us1Za5M2vQ97OWZPmODvVmepxZ8XW0UIVLGdO2T63/N3b23kCcyiwuIe0TQvMEQG8wUCCD1mA==", + "type": "package", + "path": "taglibsharp/2.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net462/TagLibSharp.dll", + "lib/net462/TagLibSharp.pdb", + "lib/net462/TaglibSharp.xml", + "lib/netstandard2.0/TagLibSharp.dll", + "lib/netstandard2.0/TagLibSharp.pdb", + "lib/netstandard2.0/TaglibSharp.xml", + "taglibsharp.2.3.0.nupkg.sha512", + "taglibsharp.nuspec" + ] + }, + "TMDbLib/2.3.0": { + "sha512": "kapjC4/ao8mxJ/G5xcZHYNVFvmAzxwWEN2PDkGbbUzAQVfbf85DsA9AUDTrXahTHYq0R7jwGyARs/y6243EPLg==", + "type": "package", + "path": "tmdblib/2.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net45/TMDbLib.dll", + "lib/net45/TMDbLib.xml", + "lib/netstandard1.2/TMDbLib.dll", + "lib/netstandard1.2/TMDbLib.xml", + "lib/netstandard2.0/TMDbLib.dll", + "lib/netstandard2.0/TMDbLib.xml", + "lib/netstandard2.1/TMDbLib.dll", + "lib/netstandard2.1/TMDbLib.xml", + "tmdblib.2.3.0.nupkg.sha512", + "tmdblib.nuspec" + ] + }, + "Ude.NetStandard/1.2.0": { + "sha512": "zRWpPAxBg3lNdm4UiKixTe+DFPoNid9CILggTCy/0WR2WKETe17kTWhiiIpLB2k5IEgnvA0QLfKlvd6Tvu0pzA==", + "type": "package", + "path": "ude.netstandard/1.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "COPYING", + "lib/net35/Ude.NetStandard.dll", + "lib/net45/Ude.NetStandard.dll", + "lib/netstandard1.0/Ude.NetStandard.dll", + "lib/netstandard1.6/Ude.NetStandard.dll", + "lib/netstandard2.0/Ude.NetStandard.dll", + "ude.netstandard.1.2.0.nupkg.sha512", + "ude.netstandard.nuspec" + ] + }, + "UTF.Unknown/2.6.0": { + "sha512": "YmmUcR9XKh3twmSjq6l0HD7qm7bj3X+gXCsdkWwpiuiB4wzGDJuCOneOQ3NvwQQEMqNeyVZOPO/EwFD4fuUoOQ==", + "type": "package", + "path": "utf.unknown/2.6.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net6.0/UtfUnknown.dll", + "lib/net6.0/UtfUnknown.xml", + "lib/net8.0/UtfUnknown.dll", + "lib/net8.0/UtfUnknown.xml", + "lib/netstandard2.0/UtfUnknown.dll", + "lib/netstandard2.0/UtfUnknown.xml", + "utf.unknown.2.6.0.nupkg.sha512", + "utf.unknown.nuspec" + ] + }, + "z440.atl.core/7.11.0": { + "sha512": "fQ4Ezr3LgTy4uRwlB4Gk6qZHlyox+RhIwCubUrq/FnLtWt406t4aSXqAN33sTxa/H0xNgZE6eL18xqpS59ErEg==", + "type": "package", + "path": "z440.atl.core/7.11.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net6.0/ATL.dll", + "lib/net6.0/ATL.xml", + "z440.atl.core.7.11.0.nupkg.sha512", + "z440.atl.core.nuspec" + ] + }, + "zlib.net-mutliplatform/1.0.8": { + "sha512": "6sYoMfSmDdfG8HgxXi0ydwCMUv5Ia1gMRh2Q0JLlTUxE9ZeWJ9dir/1Hq5MW6JcOZARRsZIOGecVx6WuAoloEA==", + "type": "package", + "path": "zlib.net-mutliplatform/1.0.8", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/netstandard1.6/zlib.net.dll", + "lib/netstandard2.0/zlib.net.dll", + "lib/netstandard2.1/zlib.net.dll", + "zlib.net-mutliplatform.1.0.8.nupkg.sha512", + "zlib.net-mutliplatform.nuspec" + ] + }, + "Emby.Photos/1.0.0": { + "type": "project", + "path": "../Emby.Photos/Emby.Photos.csproj", + "msbuildProject": "../Emby.Photos/Emby.Photos.csproj" + }, + "Emby.Server.Implementations/1.0.0": { + "type": "project", + "path": "../Emby.Server.Implementations/Emby.Server.Implementations.csproj", + "msbuildProject": "../Emby.Server.Implementations/Emby.Server.Implementations.csproj" + }, + "Jellyfin.Api/1.0.0": { + "type": "project", + "path": "../Jellyfin.Api/Jellyfin.Api.csproj", + "msbuildProject": "../Jellyfin.Api/Jellyfin.Api.csproj" + }, + "Jellyfin.CodeAnalysis/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "msbuildProject": "../src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "Jellyfin.Common/10.12.0": { + "type": "project", + "path": "../MediaBrowser.Common/MediaBrowser.Common.csproj", + "msbuildProject": "../MediaBrowser.Common/MediaBrowser.Common.csproj" + }, + "Jellyfin.Controller/10.12.0": { + "type": "project", + "path": "../MediaBrowser.Controller/MediaBrowser.Controller.csproj", + "msbuildProject": "../MediaBrowser.Controller/MediaBrowser.Controller.csproj" + }, + "Jellyfin.Data/10.12.0": { + "type": "project", + "path": "../Jellyfin.Data/Jellyfin.Data.csproj", + "msbuildProject": "../Jellyfin.Data/Jellyfin.Data.csproj" + }, + "Jellyfin.Database.Implementations/10.11.0": { + "type": "project", + "path": "../src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "msbuildProject": "../src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + }, + "Jellyfin.Database.Providers.Sqlite/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj", + "msbuildProject": "../src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj" + }, + "Jellyfin.Drawing/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.Drawing/Jellyfin.Drawing.csproj", + "msbuildProject": "../src/Jellyfin.Drawing/Jellyfin.Drawing.csproj" + }, + "Jellyfin.Drawing.Skia/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj", + "msbuildProject": "../src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj" + }, + "Jellyfin.Extensions/10.12.0": { + "type": "project", + "path": "../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "msbuildProject": "../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" + }, + "Jellyfin.LiveTv/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj", + "msbuildProject": "../src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj" + }, + "Jellyfin.MediaEncoding.Hls/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj", + "msbuildProject": "../src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj" + }, + "Jellyfin.MediaEncoding.Keyframes/10.11.0": { + "type": "project", + "path": "../src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj", + "msbuildProject": "../src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj" + }, + "Jellyfin.Model/10.12.0": { + "type": "project", + "path": "../MediaBrowser.Model/MediaBrowser.Model.csproj", + "msbuildProject": "../MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "Jellyfin.Naming/10.12.0": { + "type": "project", + "path": "../Emby.Naming/Emby.Naming.csproj", + "msbuildProject": "../Emby.Naming/Emby.Naming.csproj" + }, + "Jellyfin.Networking/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.Networking/Jellyfin.Networking.csproj", + "msbuildProject": "../src/Jellyfin.Networking/Jellyfin.Networking.csproj" + }, + "Jellyfin.Server.Implementations/1.0.0": { + "type": "project", + "path": "../Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj", + "msbuildProject": "../Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj" + }, + "MediaBrowser.LocalMetadata/1.0.0": { + "type": "project", + "path": "../MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj", + "msbuildProject": "../MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj" + }, + "MediaBrowser.MediaEncoding/1.0.0": { + "type": "project", + "path": "../MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj", + "msbuildProject": "../MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj" + }, + "MediaBrowser.Providers/1.0.0": { + "type": "project", + "path": "../MediaBrowser.Providers/MediaBrowser.Providers.csproj", + "msbuildProject": "../MediaBrowser.Providers/MediaBrowser.Providers.csproj" + }, + "MediaBrowser.XbmcMetadata/1.0.0": { + "type": "project", + "path": "../MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj", + "msbuildProject": "../MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj" + } + }, + "projectFileDependencyGroups": { + "net10.0": [ + "CommandLineParser >= 2.9.1", + "Emby.Server.Implementations >= 1.0.0", + "IDisposableAnalyzers >= 4.0.8", + "Jellyfin.CodeAnalysis >= 1.0.0", + "Jellyfin.Database.Implementations >= 10.11.0", + "Jellyfin.Drawing >= 1.0.0", + "Jellyfin.Drawing.Skia >= 1.0.0", + "Jellyfin.LiveTv >= 1.0.0", + "Jellyfin.MediaEncoding.Hls >= 1.0.0", + "Jellyfin.Server.Implementations >= 1.0.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers >= 4.14.0", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore >= 10.0.3", + "Morestachio >= 5.0.1.631", + "Serilog.AspNetCore >= 10.0.0", + "Serilog.Enrichers.Thread >= 4.0.0", + "Serilog.Expressions >= 5.0.0", + "Serilog.Settings.Configuration >= 10.0.0", + "Serilog.Sinks.Async >= 2.1.0", + "Serilog.Sinks.Console >= 6.1.1", + "Serilog.Sinks.File >= 7.0.0", + "Serilog.Sinks.Graylog >= 3.1.1", + "SerilogAnalyzer >= 0.15.0", + "SmartAnalyzers.MultithreadingAnalyzer >= 1.1.31", + "StyleCop.Analyzers >= 1.2.0-beta.556", + "prometheus-net >= 8.2.1", + "prometheus-net.AspNetCore >= 8.2.1" + ] + }, + "packageFolders": { + "/home/wjones/.nuget/packages/": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server/Jellyfin.Server.csproj", + "projectName": "jellyfin", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server/Jellyfin.Server.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/Emby.Server.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/Emby.Server.Implementations.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "CommandLineParser": { + "target": "Package", + "version": "[2.9.1, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Morestachio": { + "target": "Package", + "version": "[5.0.1.631, )", + "versionCentrallyManaged": true + }, + "Serilog.AspNetCore": { + "target": "Package", + "version": "[10.0.0, )", + "versionCentrallyManaged": true + }, + "Serilog.Enrichers.Thread": { + "target": "Package", + "version": "[4.0.0, )", + "versionCentrallyManaged": true + }, + "Serilog.Expressions": { + "target": "Package", + "version": "[5.0.0, )", + "versionCentrallyManaged": true + }, + "Serilog.Settings.Configuration": { + "target": "Package", + "version": "[10.0.0, )", + "versionCentrallyManaged": true + }, + "Serilog.Sinks.Async": { + "target": "Package", + "version": "[2.1.0, )", + "versionCentrallyManaged": true + }, + "Serilog.Sinks.Console": { + "target": "Package", + "version": "[6.1.1, )", + "versionCentrallyManaged": true + }, + "Serilog.Sinks.File": { + "target": "Package", + "version": "[7.0.0, )", + "versionCentrallyManaged": true + }, + "Serilog.Sinks.Graylog": { + "target": "Package", + "version": "[3.1.1, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + }, + "prometheus-net": { + "target": "Package", + "version": "[8.2.1, )", + "versionCentrallyManaged": true + }, + "prometheus-net.AspNetCore": { + "target": "Package", + "version": "[8.2.1, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.AspNetCore": "(,10.0.32767]", + "Microsoft.AspNetCore.Antiforgery": "(,10.0.32767]", + "Microsoft.AspNetCore.App": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.BearerToken": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Cookies": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.OAuth": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization.Policy": "(,10.0.32767]", + "Microsoft.AspNetCore.Components": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Endpoints": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Forms": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Server": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Web": "(,10.0.32767]", + "Microsoft.AspNetCore.Connections.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.CookiePolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.Internal": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.AspNetCore.HostFiltering": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Html.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Features": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Results": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpLogging": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpOverrides": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpsPolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Identity": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Metadata": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ApiExplorer": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.DataAnnotations": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Xml": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.RazorPages": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.TagHelpers": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ViewFeatures": "(,10.0.32767]", + "Microsoft.AspNetCore.OutputCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.RateLimiting": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor.Runtime": "(,10.0.32767]", + "Microsoft.AspNetCore.RequestDecompression": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCompression": "(,10.0.32767]", + "Microsoft.AspNetCore.Rewrite": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.HttpSys": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IIS": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IISIntegration": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets": "(,10.0.32767]", + "Microsoft.AspNetCore.Session": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticAssets": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticFiles": "(,10.0.32767]", + "Microsoft.AspNetCore.WebSockets": "(,10.0.32767]", + "Microsoft.AspNetCore.WebUtilities": "(,10.0.32767]", + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.Extensions.Caching.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Caching.Memory": "(,10.0.32767]", + "Microsoft.Extensions.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Binder": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.CommandLine": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.FileExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Ini": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Json": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.KeyPerFile": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.UserSecrets": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Xml": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Features": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Composite": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Physical": "(,10.0.32767]", + "Microsoft.Extensions.FileSystemGlobbing": "(,10.0.32767]", + "Microsoft.Extensions.Hosting": "(,10.0.32767]", + "Microsoft.Extensions.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Http": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Core": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Stores": "(,10.0.32767]", + "Microsoft.Extensions.Localization": "(,10.0.32767]", + "Microsoft.Extensions.Localization.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Console": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Debug": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventLog": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventSource": "(,10.0.32767]", + "Microsoft.Extensions.Logging.TraceSource": "(,10.0.32767]", + "Microsoft.Extensions.ObjectPool": "(,10.0.32767]", + "Microsoft.Extensions.Options": "(,10.0.32767]", + "Microsoft.Extensions.Options.ConfigurationExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Options.DataAnnotations": "(,10.0.32767]", + "Microsoft.Extensions.Primitives": "(,10.0.32767]", + "Microsoft.Extensions.Validation": "(,10.0.32767]", + "Microsoft.Extensions.WebEncoders": "(,10.0.32767]", + "Microsoft.JSInterop": "(,10.0.32767]", + "Microsoft.Net.Http.Headers": "(,10.0.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.EventLog": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Cbor": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Cryptography.Xml": "(,10.0.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.RateLimiting": "(,10.0.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + } +} \ No newline at end of file diff --git a/Jellyfin.Server/obj/project.nuget.cache b/Jellyfin.Server/obj/project.nuget.cache new file mode 100644 index 00000000..133b2a2f --- /dev/null +++ b/Jellyfin.Server/obj/project.nuget.cache @@ -0,0 +1,102 @@ +{ + "version": 2, + "dgSpecHash": "qBb3Dohrktw=", + "success": true, + "projectFilePath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server/Jellyfin.Server.csproj", + "expectedPackageFiles": [ + "/home/wjones/.nuget/packages/asynckeyedlock/8.0.2/asynckeyedlock.8.0.2.nupkg.sha512", + "/home/wjones/.nuget/packages/bdinfo/0.8.0/bdinfo.0.8.0.nupkg.sha512", + "/home/wjones/.nuget/packages/bitfaster.caching/2.5.4/bitfaster.caching.2.5.4.nupkg.sha512", + "/home/wjones/.nuget/packages/blurhashsharp/1.4.0-pre.1/blurhashsharp.1.4.0-pre.1.nupkg.sha512", + "/home/wjones/.nuget/packages/blurhashsharp.skiasharp/1.4.0-pre.1/blurhashsharp.skiasharp.1.4.0-pre.1.nupkg.sha512", + "/home/wjones/.nuget/packages/commandlineparser/2.9.1/commandlineparser.2.9.1.nupkg.sha512", + "/home/wjones/.nuget/packages/diacritics/4.1.4/diacritics.4.1.4.nupkg.sha512", + "/home/wjones/.nuget/packages/discutils.core/0.16.13/discutils.core.0.16.13.nupkg.sha512", + "/home/wjones/.nuget/packages/discutils.iso9660/0.16.13/discutils.iso9660.0.16.13.nupkg.sha512", + "/home/wjones/.nuget/packages/discutils.streams/0.16.13/discutils.streams.0.16.13.nupkg.sha512", + "/home/wjones/.nuget/packages/discutils.udf/0.16.13/discutils.udf.0.16.13.nupkg.sha512", + "/home/wjones/.nuget/packages/dotnet.glob/3.1.3/dotnet.glob.3.1.3.nupkg.sha512", + "/home/wjones/.nuget/packages/excss/4.3.1/excss.4.3.1.nupkg.sha512", + "/home/wjones/.nuget/packages/harfbuzzsharp/8.3.0.1/harfbuzzsharp.8.3.0.1.nupkg.sha512", + "/home/wjones/.nuget/packages/harfbuzzsharp.nativeassets.linux/8.3.1.1/harfbuzzsharp.nativeassets.linux.8.3.1.1.nupkg.sha512", + "/home/wjones/.nuget/packages/harfbuzzsharp.nativeassets.macos/8.3.0.1/harfbuzzsharp.nativeassets.macos.8.3.0.1.nupkg.sha512", + "/home/wjones/.nuget/packages/harfbuzzsharp.nativeassets.win32/8.3.0.1/harfbuzzsharp.nativeassets.win32.8.3.0.1.nupkg.sha512", + "/home/wjones/.nuget/packages/icu4n/60.1.0-alpha.356/icu4n.60.1.0-alpha.356.nupkg.sha512", + "/home/wjones/.nuget/packages/icu4n.transliterator/60.1.0-alpha.356/icu4n.transliterator.60.1.0-alpha.356.nupkg.sha512", + "/home/wjones/.nuget/packages/idisposableanalyzers/4.0.8/idisposableanalyzers.4.0.8.nupkg.sha512", + "/home/wjones/.nuget/packages/ignore/0.2.1/ignore.0.2.1.nupkg.sha512", + "/home/wjones/.nuget/packages/j2n/2.0.0/j2n.2.0.0.nupkg.sha512", + "/home/wjones/.nuget/packages/jellyfin.xmltv/10.8.0/jellyfin.xmltv.10.8.0.nupkg.sha512", + "/home/wjones/.nuget/packages/libse/4.0.12/libse.4.0.12.nupkg.sha512", + "/home/wjones/.nuget/packages/lrcparser/2025.623.0/lrcparser.2025.623.0.nupkg.sha512", + "/home/wjones/.nuget/packages/metabrainz.common/4.1.1/metabrainz.common.4.1.1.nupkg.sha512", + "/home/wjones/.nuget/packages/metabrainz.common.json/7.2.0/metabrainz.common.json.7.2.0.nupkg.sha512", + "/home/wjones/.nuget/packages/metabrainz.musicbrainz/8.0.1/metabrainz.musicbrainz.8.0.1.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0/microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.data.sqlite/10.0.3/microsoft.data.sqlite.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.data.sqlite.core/10.0.3/microsoft.data.sqlite.core.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore/10.0.3/microsoft.entityframeworkcore.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.abstractions/10.0.3/microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.analyzers/10.0.3/microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.relational/10.0.3/microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.sqlite/10.0.3/microsoft.entityframeworkcore.sqlite.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.sqlite.core/10.0.3/microsoft.entityframeworkcore.sqlite.core.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.apidescription.server/6.0.5/microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.dependencymodel/10.0.3/microsoft.extensions.dependencymodel.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.extensions.diagnostics.healthchecks.entityframeworkcore/10.0.3/microsoft.extensions.diagnostics.healthchecks.entityframeworkcore.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.openapi/1.6.22/microsoft.openapi.1.6.22.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.win32.systemevents/9.0.2/microsoft.win32.systemevents.9.0.2.nupkg.sha512", + "/home/wjones/.nuget/packages/morestachio/5.0.1.631/morestachio.5.0.1.631.nupkg.sha512", + "/home/wjones/.nuget/packages/nebml/1.1.0.5/nebml.1.1.0.5.nupkg.sha512", + "/home/wjones/.nuget/packages/newtonsoft.json/13.0.4/newtonsoft.json.13.0.4.nupkg.sha512", + "/home/wjones/.nuget/packages/playlistsnet/1.4.1/playlistsnet.1.4.1.nupkg.sha512", + "/home/wjones/.nuget/packages/polly/8.6.5/polly.8.6.5.nupkg.sha512", + "/home/wjones/.nuget/packages/polly.core/8.6.5/polly.core.8.6.5.nupkg.sha512", + "/home/wjones/.nuget/packages/prometheus-net/8.2.1/prometheus-net.8.2.1.nupkg.sha512", + "/home/wjones/.nuget/packages/prometheus-net.aspnetcore/8.2.1/prometheus-net.aspnetcore.8.2.1.nupkg.sha512", + "/home/wjones/.nuget/packages/prometheus-net.dotnetruntime/4.4.1/prometheus-net.dotnetruntime.4.4.1.nupkg.sha512", + "/home/wjones/.nuget/packages/serilog/4.3.0/serilog.4.3.0.nupkg.sha512", + "/home/wjones/.nuget/packages/serilog.aspnetcore/10.0.0/serilog.aspnetcore.10.0.0.nupkg.sha512", + "/home/wjones/.nuget/packages/serilog.enrichers.thread/4.0.0/serilog.enrichers.thread.4.0.0.nupkg.sha512", + "/home/wjones/.nuget/packages/serilog.expressions/5.0.0/serilog.expressions.5.0.0.nupkg.sha512", + "/home/wjones/.nuget/packages/serilog.extensions.hosting/10.0.0/serilog.extensions.hosting.10.0.0.nupkg.sha512", + "/home/wjones/.nuget/packages/serilog.extensions.logging/10.0.0/serilog.extensions.logging.10.0.0.nupkg.sha512", + "/home/wjones/.nuget/packages/serilog.formatting.compact/3.0.0/serilog.formatting.compact.3.0.0.nupkg.sha512", + "/home/wjones/.nuget/packages/serilog.settings.configuration/10.0.0/serilog.settings.configuration.10.0.0.nupkg.sha512", + "/home/wjones/.nuget/packages/serilog.sinks.async/2.1.0/serilog.sinks.async.2.1.0.nupkg.sha512", + "/home/wjones/.nuget/packages/serilog.sinks.console/6.1.1/serilog.sinks.console.6.1.1.nupkg.sha512", + "/home/wjones/.nuget/packages/serilog.sinks.debug/3.0.0/serilog.sinks.debug.3.0.0.nupkg.sha512", + "/home/wjones/.nuget/packages/serilog.sinks.file/7.0.0/serilog.sinks.file.7.0.0.nupkg.sha512", + "/home/wjones/.nuget/packages/serilog.sinks.graylog/3.1.1/serilog.sinks.graylog.3.1.1.nupkg.sha512", + "/home/wjones/.nuget/packages/seriloganalyzer/0.15.0/seriloganalyzer.0.15.0.nupkg.sha512", + "/home/wjones/.nuget/packages/shimskiasharp/3.4.1/shimskiasharp.3.4.1.nupkg.sha512", + "/home/wjones/.nuget/packages/skiasharp/3.116.1/skiasharp.3.116.1.nupkg.sha512", + "/home/wjones/.nuget/packages/skiasharp.harfbuzz/3.116.1/skiasharp.harfbuzz.3.116.1.nupkg.sha512", + "/home/wjones/.nuget/packages/skiasharp.nativeassets.linux/3.116.1/skiasharp.nativeassets.linux.3.116.1.nupkg.sha512", + "/home/wjones/.nuget/packages/skiasharp.nativeassets.macos/3.116.1/skiasharp.nativeassets.macos.3.116.1.nupkg.sha512", + "/home/wjones/.nuget/packages/skiasharp.nativeassets.win32/3.116.1/skiasharp.nativeassets.win32.3.116.1.nupkg.sha512", + "/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31/smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512", + "/home/wjones/.nuget/packages/sqlitepclraw.bundle_e_sqlite3/2.1.11/sqlitepclraw.bundle_e_sqlite3.2.1.11.nupkg.sha512", + "/home/wjones/.nuget/packages/sqlitepclraw.core/2.1.11/sqlitepclraw.core.2.1.11.nupkg.sha512", + "/home/wjones/.nuget/packages/sqlitepclraw.lib.e_sqlite3/2.1.11/sqlitepclraw.lib.e_sqlite3.2.1.11.nupkg.sha512", + "/home/wjones/.nuget/packages/sqlitepclraw.provider.e_sqlite3/2.1.11/sqlitepclraw.provider.e_sqlite3.2.1.11.nupkg.sha512", + "/home/wjones/.nuget/packages/stylecop.analyzers/1.2.0-beta.556/stylecop.analyzers.1.2.0-beta.556.nupkg.sha512", + "/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556/stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512", + "/home/wjones/.nuget/packages/svg.custom/3.4.1/svg.custom.3.4.1.nupkg.sha512", + "/home/wjones/.nuget/packages/svg.model/3.4.1/svg.model.3.4.1.nupkg.sha512", + "/home/wjones/.nuget/packages/svg.skia/3.4.1/svg.skia.3.4.1.nupkg.sha512", + "/home/wjones/.nuget/packages/swashbuckle.aspnetcore/7.3.2/swashbuckle.aspnetcore.7.3.2.nupkg.sha512", + "/home/wjones/.nuget/packages/swashbuckle.aspnetcore.redoc/6.9.0/swashbuckle.aspnetcore.redoc.6.9.0.nupkg.sha512", + "/home/wjones/.nuget/packages/swashbuckle.aspnetcore.swagger/7.3.2/swashbuckle.aspnetcore.swagger.7.3.2.nupkg.sha512", + "/home/wjones/.nuget/packages/swashbuckle.aspnetcore.swaggergen/7.3.2/swashbuckle.aspnetcore.swaggergen.7.3.2.nupkg.sha512", + "/home/wjones/.nuget/packages/swashbuckle.aspnetcore.swaggerui/7.3.2/swashbuckle.aspnetcore.swaggerui.7.3.2.nupkg.sha512", + "/home/wjones/.nuget/packages/system.drawing.common/9.0.2/system.drawing.common.9.0.2.nupkg.sha512", + "/home/wjones/.nuget/packages/taglibsharp/2.3.0/taglibsharp.2.3.0.nupkg.sha512", + "/home/wjones/.nuget/packages/tmdblib/2.3.0/tmdblib.2.3.0.nupkg.sha512", + "/home/wjones/.nuget/packages/ude.netstandard/1.2.0/ude.netstandard.1.2.0.nupkg.sha512", + "/home/wjones/.nuget/packages/utf.unknown/2.6.0/utf.unknown.2.6.0.nupkg.sha512", + "/home/wjones/.nuget/packages/z440.atl.core/7.11.0/z440.atl.core.7.11.0.nupkg.sha512", + "/home/wjones/.nuget/packages/zlib.net-mutliplatform/1.0.8/zlib.net-mutliplatform.1.0.8.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Jellyfin.Server/obj/project.packagespec.json b/Jellyfin.Server/obj/project.packagespec.json new file mode 100644 index 00000000..49fb1896 --- /dev/null +++ b/Jellyfin.Server/obj/project.packagespec.json @@ -0,0 +1 @@ +"restore":{"projectUniqueName":"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server/Jellyfin.Server.csproj","projectName":"jellyfin","projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server/Jellyfin.Server.csproj","outputPath":"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server/obj/","projectStyle":"PackageReference","centralPackageVersionsManagementEnabled":true,"originalTargetFrameworks":["net10.0"],"sources":{"/usr/lib/dotnet/library-packs":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net10.0":{"targetAlias":"net10.0","projectReferences":{"/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/Emby.Server.Implementations.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/Emby.Server.Implementations/Emby.Server.Implementations.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj"}}}},"warningProperties":{"allWarningsAsErrors":true,"warnAsError":["NU1605"],"warnNotAsError":["NU1902","NU1903"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"all"},"SdkAnalysisLevel":"10.0.100"}"frameworks":{"net10.0":{"targetAlias":"net10.0","dependencies":{"CommandLineParser":{"target":"Package","version":"[2.9.1, )","versionCentrallyManaged":true},"IDisposableAnalyzers":{"include":"Runtime, Build, Native, ContentFiles, Analyzers","suppressParent":"All","target":"Package","version":"[4.0.8, )","versionCentrallyManaged":true},"Microsoft.CodeAnalysis.BannedApiAnalyzers":{"include":"Runtime, Build, Native, ContentFiles, Analyzers","suppressParent":"All","target":"Package","version":"[4.14.0, )","versionCentrallyManaged":true},"Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore":{"target":"Package","version":"[10.0.3, )","versionCentrallyManaged":true},"Morestachio":{"target":"Package","version":"[5.0.1.631, )","versionCentrallyManaged":true},"Serilog.AspNetCore":{"target":"Package","version":"[10.0.0, )","versionCentrallyManaged":true},"Serilog.Enrichers.Thread":{"target":"Package","version":"[4.0.0, )","versionCentrallyManaged":true},"Serilog.Expressions":{"target":"Package","version":"[5.0.0, )","versionCentrallyManaged":true},"Serilog.Settings.Configuration":{"target":"Package","version":"[10.0.0, )","versionCentrallyManaged":true},"Serilog.Sinks.Async":{"target":"Package","version":"[2.1.0, )","versionCentrallyManaged":true},"Serilog.Sinks.Console":{"target":"Package","version":"[6.1.1, )","versionCentrallyManaged":true},"Serilog.Sinks.File":{"target":"Package","version":"[7.0.0, )","versionCentrallyManaged":true},"Serilog.Sinks.Graylog":{"target":"Package","version":"[3.1.1, )","versionCentrallyManaged":true},"SerilogAnalyzer":{"suppressParent":"All","target":"Package","version":"[0.15.0, )","versionCentrallyManaged":true},"SmartAnalyzers.MultithreadingAnalyzer":{"suppressParent":"All","target":"Package","version":"[1.1.31, )","versionCentrallyManaged":true},"StyleCop.Analyzers":{"suppressParent":"All","target":"Package","version":"[1.2.0-beta.556, )","versionCentrallyManaged":true},"prometheus-net":{"target":"Package","version":"[8.2.1, )","versionCentrallyManaged":true},"prometheus-net.AspNetCore":{"target":"Package","version":"[8.2.1, )","versionCentrallyManaged":true}},"centralPackageVersions":{"AsyncKeyedLock":"8.0.2","AutoFixture":"4.18.1","AutoFixture.AutoMoq":"4.18.1","AutoFixture.Xunit2":"4.18.1","BDInfo":"0.8.0","BitFaster.Caching":"2.5.4","BlurHashSharp":"1.4.0-pre.1","BlurHashSharp.SkiaSharp":"1.4.0-pre.1","CommandLineParser":"2.9.1","coverlet.collector":"8.0.0","Diacritics":"4.1.4","DiscUtils.Udf":"0.16.13","DotNet.Glob":"3.1.3","FsCheck.Xunit":"3.3.2","HarfBuzzSharp.NativeAssets.Linux":"8.3.1.1","ICU4N.Transliterator":"60.1.0-alpha.356","IDisposableAnalyzers":"4.0.8","Ignore":"0.2.1","Jellyfin.XmlTv":"10.8.0","libse":"4.0.12","LrcParser":"2025.623.0","MetaBrainz.MusicBrainz":"8.0.1","Microsoft.AspNetCore.Authorization":"10.0.3","Microsoft.AspNetCore.Mvc.Testing":"10.0.3","Microsoft.CodeAnalysis.Analyzers":"3.11.0","Microsoft.CodeAnalysis.BannedApiAnalyzers":"4.14.0","Microsoft.CodeAnalysis.Common":"5.0.0","Microsoft.CodeAnalysis.CSharp":"5.0.0","Microsoft.Data.Sqlite":"10.0.3","Microsoft.EntityFrameworkCore.Design":"10.0.3","Microsoft.EntityFrameworkCore.Relational":"10.0.3","Microsoft.EntityFrameworkCore.Sqlite":"10.0.3","Microsoft.EntityFrameworkCore.Tools":"10.0.3","Microsoft.Extensions.Caching.Abstractions":"10.0.3","Microsoft.Extensions.Caching.Memory":"10.0.3","Microsoft.Extensions.Configuration.Abstractions":"10.0.3","Microsoft.Extensions.Configuration.Binder":"10.0.3","Microsoft.Extensions.DependencyInjection":"10.0.3","Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore":"10.0.3","Microsoft.Extensions.Hosting.Abstractions":"10.0.3","Microsoft.Extensions.Http":"10.0.3","Microsoft.Extensions.Logging":"10.0.3","Microsoft.Extensions.Options":"10.0.3","Microsoft.NET.Test.Sdk":"18.0.1","MimeTypes":"2.5.2","Moq":"4.18.4","Morestachio":"5.0.1.631","NEbml":"1.1.0.5","Newtonsoft.Json":"13.0.4","PlaylistsNET":"1.4.1","Polly":"8.6.5","prometheus-net":"8.2.1","prometheus-net.AspNetCore":"8.2.1","prometheus-net.DotNetRuntime":"4.4.1","Serilog.AspNetCore":"10.0.0","Serilog.Enrichers.Thread":"4.0.0","Serilog.Expressions":"5.0.0","Serilog.Settings.Configuration":"10.0.0","Serilog.Sinks.Async":"2.1.0","Serilog.Sinks.Console":"6.1.1","Serilog.Sinks.File":"7.0.0","Serilog.Sinks.Graylog":"3.1.1","SerilogAnalyzer":"0.15.0","SharpFuzz":"2.2.0","SkiaSharp":"[3.116.1]","SkiaSharp.HarfBuzz":"[3.116.1]","SkiaSharp.NativeAssets.Linux":"[3.116.1]","SmartAnalyzers.MultithreadingAnalyzer":"1.1.31","StyleCop.Analyzers":"1.2.0-beta.556","Svg.Skia":"3.4.1","Swashbuckle.AspNetCore":"7.3.2","Swashbuckle.AspNetCore.ReDoc":"6.9.0","System.Text.Json":"10.0.3","TagLibSharp":"2.3.0","TMDbLib":"2.3.0","UTF.Unknown":"2.6.0","xunit":"2.9.3","Xunit.Priority":"1.1.6","xunit.runner.visualstudio":"2.8.2","Xunit.SkippableFact":"1.5.61","z440.atl.core":"7.11.0"},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.AspNetCore.App":{"privateAssets":"none"},"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json","packagesToPrune":{"Microsoft.AspNetCore":"(,10.0.32767]","Microsoft.AspNetCore.Antiforgery":"(,10.0.32767]","Microsoft.AspNetCore.App":"(,10.0.32767]","Microsoft.AspNetCore.Authentication":"(,10.0.32767]","Microsoft.AspNetCore.Authentication.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.Authentication.BearerToken":"(,10.0.32767]","Microsoft.AspNetCore.Authentication.Cookies":"(,10.0.32767]","Microsoft.AspNetCore.Authentication.Core":"(,10.0.32767]","Microsoft.AspNetCore.Authentication.OAuth":"(,10.0.32767]","Microsoft.AspNetCore.Authorization":"(,10.0.32767]","Microsoft.AspNetCore.Authorization.Policy":"(,10.0.32767]","Microsoft.AspNetCore.Components":"(,10.0.32767]","Microsoft.AspNetCore.Components.Authorization":"(,10.0.32767]","Microsoft.AspNetCore.Components.Endpoints":"(,10.0.32767]","Microsoft.AspNetCore.Components.Forms":"(,10.0.32767]","Microsoft.AspNetCore.Components.Server":"(,10.0.32767]","Microsoft.AspNetCore.Components.Web":"(,10.0.32767]","Microsoft.AspNetCore.Connections.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.CookiePolicy":"(,10.0.32767]","Microsoft.AspNetCore.Cors":"(,10.0.32767]","Microsoft.AspNetCore.Cryptography.Internal":"(,10.0.32767]","Microsoft.AspNetCore.Cryptography.KeyDerivation":"(,10.0.32767]","Microsoft.AspNetCore.DataProtection":"(,10.0.32767]","Microsoft.AspNetCore.DataProtection.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.DataProtection.Extensions":"(,10.0.32767]","Microsoft.AspNetCore.Diagnostics":"(,10.0.32767]","Microsoft.AspNetCore.Diagnostics.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.Diagnostics.HealthChecks":"(,10.0.32767]","Microsoft.AspNetCore.HostFiltering":"(,10.0.32767]","Microsoft.AspNetCore.Hosting":"(,10.0.32767]","Microsoft.AspNetCore.Hosting.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.Hosting.Server.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.Html.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.Http":"(,10.0.32767]","Microsoft.AspNetCore.Http.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.Http.Connections":"(,10.0.32767]","Microsoft.AspNetCore.Http.Connections.Common":"(,10.0.32767]","Microsoft.AspNetCore.Http.Extensions":"(,10.0.32767]","Microsoft.AspNetCore.Http.Features":"(,10.0.32767]","Microsoft.AspNetCore.Http.Results":"(,10.0.32767]","Microsoft.AspNetCore.HttpLogging":"(,10.0.32767]","Microsoft.AspNetCore.HttpOverrides":"(,10.0.32767]","Microsoft.AspNetCore.HttpsPolicy":"(,10.0.32767]","Microsoft.AspNetCore.Identity":"(,10.0.32767]","Microsoft.AspNetCore.Localization":"(,10.0.32767]","Microsoft.AspNetCore.Localization.Routing":"(,10.0.32767]","Microsoft.AspNetCore.Metadata":"(,10.0.32767]","Microsoft.AspNetCore.Mvc":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.ApiExplorer":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.Core":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.Cors":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.DataAnnotations":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.Formatters.Json":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.Formatters.Xml":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.Localization":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.Razor":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.RazorPages":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.TagHelpers":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.ViewFeatures":"(,10.0.32767]","Microsoft.AspNetCore.OutputCaching":"(,10.0.32767]","Microsoft.AspNetCore.RateLimiting":"(,10.0.32767]","Microsoft.AspNetCore.Razor":"(,10.0.32767]","Microsoft.AspNetCore.Razor.Runtime":"(,10.0.32767]","Microsoft.AspNetCore.RequestDecompression":"(,10.0.32767]","Microsoft.AspNetCore.ResponseCaching":"(,10.0.32767]","Microsoft.AspNetCore.ResponseCaching.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.ResponseCompression":"(,10.0.32767]","Microsoft.AspNetCore.Rewrite":"(,10.0.32767]","Microsoft.AspNetCore.Routing":"(,10.0.32767]","Microsoft.AspNetCore.Routing.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.Server.HttpSys":"(,10.0.32767]","Microsoft.AspNetCore.Server.IIS":"(,10.0.32767]","Microsoft.AspNetCore.Server.IISIntegration":"(,10.0.32767]","Microsoft.AspNetCore.Server.Kestrel":"(,10.0.32767]","Microsoft.AspNetCore.Server.Kestrel.Core":"(,10.0.32767]","Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes":"(,10.0.32767]","Microsoft.AspNetCore.Server.Kestrel.Transport.Quic":"(,10.0.32767]","Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets":"(,10.0.32767]","Microsoft.AspNetCore.Session":"(,10.0.32767]","Microsoft.AspNetCore.SignalR":"(,10.0.32767]","Microsoft.AspNetCore.SignalR.Common":"(,10.0.32767]","Microsoft.AspNetCore.SignalR.Core":"(,10.0.32767]","Microsoft.AspNetCore.SignalR.Protocols.Json":"(,10.0.32767]","Microsoft.AspNetCore.StaticAssets":"(,10.0.32767]","Microsoft.AspNetCore.StaticFiles":"(,10.0.32767]","Microsoft.AspNetCore.WebSockets":"(,10.0.32767]","Microsoft.AspNetCore.WebUtilities":"(,10.0.32767]","Microsoft.CSharp":"(,4.7.32767]","Microsoft.Extensions.Caching.Abstractions":"(,10.0.32767]","Microsoft.Extensions.Caching.Memory":"(,10.0.32767]","Microsoft.Extensions.Configuration":"(,10.0.32767]","Microsoft.Extensions.Configuration.Abstractions":"(,10.0.32767]","Microsoft.Extensions.Configuration.Binder":"(,10.0.32767]","Microsoft.Extensions.Configuration.CommandLine":"(,10.0.32767]","Microsoft.Extensions.Configuration.EnvironmentVariables":"(,10.0.32767]","Microsoft.Extensions.Configuration.FileExtensions":"(,10.0.32767]","Microsoft.Extensions.Configuration.Ini":"(,10.0.32767]","Microsoft.Extensions.Configuration.Json":"(,10.0.32767]","Microsoft.Extensions.Configuration.KeyPerFile":"(,10.0.32767]","Microsoft.Extensions.Configuration.UserSecrets":"(,10.0.32767]","Microsoft.Extensions.Configuration.Xml":"(,10.0.32767]","Microsoft.Extensions.DependencyInjection":"(,10.0.32767]","Microsoft.Extensions.DependencyInjection.Abstractions":"(,10.0.32767]","Microsoft.Extensions.Diagnostics":"(,10.0.32767]","Microsoft.Extensions.Diagnostics.Abstractions":"(,10.0.32767]","Microsoft.Extensions.Diagnostics.HealthChecks":"(,10.0.32767]","Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions":"(,10.0.32767]","Microsoft.Extensions.Features":"(,10.0.32767]","Microsoft.Extensions.FileProviders.Abstractions":"(,10.0.32767]","Microsoft.Extensions.FileProviders.Composite":"(,10.0.32767]","Microsoft.Extensions.FileProviders.Physical":"(,10.0.32767]","Microsoft.Extensions.FileSystemGlobbing":"(,10.0.32767]","Microsoft.Extensions.Hosting":"(,10.0.32767]","Microsoft.Extensions.Hosting.Abstractions":"(,10.0.32767]","Microsoft.Extensions.Http":"(,10.0.32767]","Microsoft.Extensions.Identity.Core":"(,10.0.32767]","Microsoft.Extensions.Identity.Stores":"(,10.0.32767]","Microsoft.Extensions.Localization":"(,10.0.32767]","Microsoft.Extensions.Localization.Abstractions":"(,10.0.32767]","Microsoft.Extensions.Logging":"(,10.0.32767]","Microsoft.Extensions.Logging.Abstractions":"(,10.0.32767]","Microsoft.Extensions.Logging.Configuration":"(,10.0.32767]","Microsoft.Extensions.Logging.Console":"(,10.0.32767]","Microsoft.Extensions.Logging.Debug":"(,10.0.32767]","Microsoft.Extensions.Logging.EventLog":"(,10.0.32767]","Microsoft.Extensions.Logging.EventSource":"(,10.0.32767]","Microsoft.Extensions.Logging.TraceSource":"(,10.0.32767]","Microsoft.Extensions.ObjectPool":"(,10.0.32767]","Microsoft.Extensions.Options":"(,10.0.32767]","Microsoft.Extensions.Options.ConfigurationExtensions":"(,10.0.32767]","Microsoft.Extensions.Options.DataAnnotations":"(,10.0.32767]","Microsoft.Extensions.Primitives":"(,10.0.32767]","Microsoft.Extensions.Validation":"(,10.0.32767]","Microsoft.Extensions.WebEncoders":"(,10.0.32767]","Microsoft.JSInterop":"(,10.0.32767]","Microsoft.Net.Http.Headers":"(,10.0.32767]","Microsoft.VisualBasic":"(,10.4.32767]","Microsoft.Win32.Primitives":"(,4.3.32767]","Microsoft.Win32.Registry":"(,5.0.32767]","runtime.any.System.Collections":"(,4.3.32767]","runtime.any.System.Diagnostics.Tools":"(,4.3.32767]","runtime.any.System.Diagnostics.Tracing":"(,4.3.32767]","runtime.any.System.Globalization":"(,4.3.32767]","runtime.any.System.Globalization.Calendars":"(,4.3.32767]","runtime.any.System.IO":"(,4.3.32767]","runtime.any.System.Reflection":"(,4.3.32767]","runtime.any.System.Reflection.Extensions":"(,4.3.32767]","runtime.any.System.Reflection.Primitives":"(,4.3.32767]","runtime.any.System.Resources.ResourceManager":"(,4.3.32767]","runtime.any.System.Runtime":"(,4.3.32767]","runtime.any.System.Runtime.Handles":"(,4.3.32767]","runtime.any.System.Runtime.InteropServices":"(,4.3.32767]","runtime.any.System.Text.Encoding":"(,4.3.32767]","runtime.any.System.Text.Encoding.Extensions":"(,4.3.32767]","runtime.any.System.Threading.Tasks":"(,4.3.32767]","runtime.any.System.Threading.Timer":"(,4.3.32767]","runtime.aot.System.Collections":"(,4.3.32767]","runtime.aot.System.Diagnostics.Tools":"(,4.3.32767]","runtime.aot.System.Diagnostics.Tracing":"(,4.3.32767]","runtime.aot.System.Globalization":"(,4.3.32767]","runtime.aot.System.Globalization.Calendars":"(,4.3.32767]","runtime.aot.System.IO":"(,4.3.32767]","runtime.aot.System.Reflection":"(,4.3.32767]","runtime.aot.System.Reflection.Extensions":"(,4.3.32767]","runtime.aot.System.Reflection.Primitives":"(,4.3.32767]","runtime.aot.System.Resources.ResourceManager":"(,4.3.32767]","runtime.aot.System.Runtime":"(,4.3.32767]","runtime.aot.System.Runtime.Handles":"(,4.3.32767]","runtime.aot.System.Runtime.InteropServices":"(,4.3.32767]","runtime.aot.System.Text.Encoding":"(,4.3.32767]","runtime.aot.System.Text.Encoding.Extensions":"(,4.3.32767]","runtime.aot.System.Threading.Tasks":"(,4.3.32767]","runtime.aot.System.Threading.Timer":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.unix.Microsoft.Win32.Primitives":"(,4.3.32767]","runtime.unix.System.Console":"(,4.3.32767]","runtime.unix.System.Diagnostics.Debug":"(,4.3.32767]","runtime.unix.System.IO.FileSystem":"(,4.3.32767]","runtime.unix.System.Net.Primitives":"(,4.3.32767]","runtime.unix.System.Net.Sockets":"(,4.3.32767]","runtime.unix.System.Private.Uri":"(,4.3.32767]","runtime.unix.System.Runtime.Extensions":"(,4.3.32767]","runtime.win.Microsoft.Win32.Primitives":"(,4.3.32767]","runtime.win.System.Console":"(,4.3.32767]","runtime.win.System.Diagnostics.Debug":"(,4.3.32767]","runtime.win.System.IO.FileSystem":"(,4.3.32767]","runtime.win.System.Net.Primitives":"(,4.3.32767]","runtime.win.System.Net.Sockets":"(,4.3.32767]","runtime.win.System.Runtime.Extensions":"(,4.3.32767]","runtime.win10-arm-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win10-arm64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win10-x64-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win10-x86-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win7-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win7-x86.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win7.System.Private.Uri":"(,4.3.32767]","runtime.win8-arm.runtime.native.System.IO.Compression":"(,4.3.32767]","System.AppContext":"(,4.3.32767]","System.Buffers":"(,5.0.32767]","System.Collections":"(,4.3.32767]","System.Collections.Concurrent":"(,4.3.32767]","System.Collections.Immutable":"(,10.0.32767]","System.Collections.NonGeneric":"(,4.3.32767]","System.Collections.Specialized":"(,4.3.32767]","System.ComponentModel":"(,4.3.32767]","System.ComponentModel.Annotations":"(,4.3.32767]","System.ComponentModel.EventBasedAsync":"(,4.3.32767]","System.ComponentModel.Primitives":"(,4.3.32767]","System.ComponentModel.TypeConverter":"(,4.3.32767]","System.Console":"(,4.3.32767]","System.Data.Common":"(,4.3.32767]","System.Data.DataSetExtensions":"(,4.4.32767]","System.Diagnostics.Contracts":"(,4.3.32767]","System.Diagnostics.Debug":"(,4.3.32767]","System.Diagnostics.DiagnosticSource":"(,10.0.32767]","System.Diagnostics.EventLog":"(,10.0.32767]","System.Diagnostics.FileVersionInfo":"(,4.3.32767]","System.Diagnostics.Process":"(,4.3.32767]","System.Diagnostics.StackTrace":"(,4.3.32767]","System.Diagnostics.TextWriterTraceListener":"(,4.3.32767]","System.Diagnostics.Tools":"(,4.3.32767]","System.Diagnostics.TraceSource":"(,4.3.32767]","System.Diagnostics.Tracing":"(,4.3.32767]","System.Drawing.Primitives":"(,4.3.32767]","System.Dynamic.Runtime":"(,4.3.32767]","System.Formats.Asn1":"(,10.0.32767]","System.Formats.Cbor":"(,10.0.32767]","System.Formats.Tar":"(,10.0.32767]","System.Globalization":"(,4.3.32767]","System.Globalization.Calendars":"(,4.3.32767]","System.Globalization.Extensions":"(,4.3.32767]","System.IO":"(,4.3.32767]","System.IO.Compression":"(,4.3.32767]","System.IO.Compression.ZipFile":"(,4.3.32767]","System.IO.FileSystem":"(,4.3.32767]","System.IO.FileSystem.AccessControl":"(,4.4.32767]","System.IO.FileSystem.DriveInfo":"(,4.3.32767]","System.IO.FileSystem.Primitives":"(,4.3.32767]","System.IO.FileSystem.Watcher":"(,4.3.32767]","System.IO.IsolatedStorage":"(,4.3.32767]","System.IO.MemoryMappedFiles":"(,4.3.32767]","System.IO.Pipelines":"(,10.0.32767]","System.IO.Pipes":"(,4.3.32767]","System.IO.Pipes.AccessControl":"(,5.0.32767]","System.IO.UnmanagedMemoryStream":"(,4.3.32767]","System.Linq":"(,4.3.32767]","System.Linq.AsyncEnumerable":"(,10.0.32767]","System.Linq.Expressions":"(,4.3.32767]","System.Linq.Parallel":"(,4.3.32767]","System.Linq.Queryable":"(,4.3.32767]","System.Memory":"(,5.0.32767]","System.Net.Http":"(,4.3.32767]","System.Net.Http.Json":"(,10.0.32767]","System.Net.NameResolution":"(,4.3.32767]","System.Net.NetworkInformation":"(,4.3.32767]","System.Net.Ping":"(,4.3.32767]","System.Net.Primitives":"(,4.3.32767]","System.Net.Requests":"(,4.3.32767]","System.Net.Security":"(,4.3.32767]","System.Net.ServerSentEvents":"(,10.0.32767]","System.Net.Sockets":"(,4.3.32767]","System.Net.WebHeaderCollection":"(,4.3.32767]","System.Net.WebSockets":"(,4.3.32767]","System.Net.WebSockets.Client":"(,4.3.32767]","System.Numerics.Vectors":"(,5.0.32767]","System.ObjectModel":"(,4.3.32767]","System.Private.DataContractSerialization":"(,4.3.32767]","System.Private.Uri":"(,4.3.32767]","System.Reflection":"(,4.3.32767]","System.Reflection.DispatchProxy":"(,6.0.32767]","System.Reflection.Emit":"(,4.7.32767]","System.Reflection.Emit.ILGeneration":"(,4.7.32767]","System.Reflection.Emit.Lightweight":"(,4.7.32767]","System.Reflection.Extensions":"(,4.3.32767]","System.Reflection.Metadata":"(,10.0.32767]","System.Reflection.Primitives":"(,4.3.32767]","System.Reflection.TypeExtensions":"(,4.3.32767]","System.Resources.Reader":"(,4.3.32767]","System.Resources.ResourceManager":"(,4.3.32767]","System.Resources.Writer":"(,4.3.32767]","System.Runtime":"(,4.3.32767]","System.Runtime.CompilerServices.Unsafe":"(,7.0.32767]","System.Runtime.CompilerServices.VisualC":"(,4.3.32767]","System.Runtime.Extensions":"(,4.3.32767]","System.Runtime.Handles":"(,4.3.32767]","System.Runtime.InteropServices":"(,4.3.32767]","System.Runtime.InteropServices.RuntimeInformation":"(,4.3.32767]","System.Runtime.Loader":"(,4.3.32767]","System.Runtime.Numerics":"(,4.3.32767]","System.Runtime.Serialization.Formatters":"(,4.3.32767]","System.Runtime.Serialization.Json":"(,4.3.32767]","System.Runtime.Serialization.Primitives":"(,4.3.32767]","System.Runtime.Serialization.Xml":"(,4.3.32767]","System.Security.AccessControl":"(,6.0.32767]","System.Security.Claims":"(,4.3.32767]","System.Security.Cryptography.Algorithms":"(,4.3.32767]","System.Security.Cryptography.Cng":"(,5.0.32767]","System.Security.Cryptography.Csp":"(,4.3.32767]","System.Security.Cryptography.Encoding":"(,4.3.32767]","System.Security.Cryptography.OpenSsl":"(,5.0.32767]","System.Security.Cryptography.Primitives":"(,4.3.32767]","System.Security.Cryptography.X509Certificates":"(,4.3.32767]","System.Security.Cryptography.Xml":"(,10.0.32767]","System.Security.Principal":"(,4.3.32767]","System.Security.Principal.Windows":"(,5.0.32767]","System.Security.SecureString":"(,4.3.32767]","System.Text.Encoding":"(,4.3.32767]","System.Text.Encoding.CodePages":"(,10.0.32767]","System.Text.Encoding.Extensions":"(,4.3.32767]","System.Text.Encodings.Web":"(,10.0.32767]","System.Text.Json":"(,10.0.32767]","System.Text.RegularExpressions":"(,4.3.32767]","System.Threading":"(,4.3.32767]","System.Threading.AccessControl":"(,10.0.32767]","System.Threading.Channels":"(,10.0.32767]","System.Threading.Overlapped":"(,4.3.32767]","System.Threading.RateLimiting":"(,10.0.32767]","System.Threading.Tasks":"(,4.3.32767]","System.Threading.Tasks.Dataflow":"(,10.0.32767]","System.Threading.Tasks.Extensions":"(,5.0.32767]","System.Threading.Tasks.Parallel":"(,4.3.32767]","System.Threading.Thread":"(,4.3.32767]","System.Threading.ThreadPool":"(,4.3.32767]","System.Threading.Timer":"(,4.3.32767]","System.ValueTuple":"(,4.5.32767]","System.Xml.ReaderWriter":"(,4.3.32767]","System.Xml.XDocument":"(,4.3.32767]","System.Xml.XmlDocument":"(,4.3.32767]","System.Xml.XmlSerializer":"(,4.3.32767]","System.Xml.XPath":"(,4.3.32767]","System.Xml.XPath.XDocument":"(,5.0.32767]"}}} \ No newline at end of file diff --git a/Jellyfin.Server/obj/rider.project.model.nuget.info b/Jellyfin.Server/obj/rider.project.model.nuget.info new file mode 100644 index 00000000..0fc97850 --- /dev/null +++ b/Jellyfin.Server/obj/rider.project.model.nuget.info @@ -0,0 +1 @@ +17714532055100000 \ No newline at end of file diff --git a/Jellyfin.Server/obj/rider.project.restore.info b/Jellyfin.Server/obj/rider.project.restore.info new file mode 100644 index 00000000..0828b19f --- /dev/null +++ b/Jellyfin.Server/obj/rider.project.restore.info @@ -0,0 +1 @@ +17715044200600000 \ No newline at end of file diff --git a/Jellyfin.Server/wwwroot/api-docs/jellyfin.svg b/Jellyfin.Server/wwwroot/api-docs/jellyfin.svg new file mode 100644 index 00000000..69253031 --- /dev/null +++ b/Jellyfin.Server/wwwroot/api-docs/jellyfin.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Jellyfin.Server/wwwroot/api-docs/redoc/custom.css b/Jellyfin.Server/wwwroot/api-docs/redoc/custom.css new file mode 100644 index 00000000..e69de29b diff --git a/Jellyfin.Server/wwwroot/api-docs/swagger/custom.css b/Jellyfin.Server/wwwroot/api-docs/swagger/custom.css new file mode 100644 index 00000000..c14ad602 --- /dev/null +++ b/Jellyfin.Server/wwwroot/api-docs/swagger/custom.css @@ -0,0 +1,17 @@ +/* logo */ +.topbar-wrapper img[alt="Swagger UI"], .topbar-wrapper span { + visibility: collapse; +} + +.topbar-wrapper .link:after { + content: ''; + display: block; + background-image: url(../jellyfin.svg); + background-position: center; + background-repeat: no-repeat; + background-size: contain; + box-sizing: border-box; + width: 220px; + height: 40px; +} +/* end logo */ diff --git a/Jellyfin.sln b/Jellyfin.sln new file mode 100644 index 00000000..b0d5a5eb --- /dev/null +++ b/Jellyfin.sln @@ -0,0 +1,322 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 18 +VisualStudioVersion = 18.0.11222.15 d18.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.XbmcMetadata", "MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj", "{23499896-B135-4527-8574-C26E926EA99E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.LocalMetadata", "MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj", "{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing", "src\Jellyfin.Drawing\Jellyfin.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Photos", "Emby.Photos\Emby.Photos.csproj", "{89AB4548-770D-41FD-A891-8DAFF44F452C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Server.Implementations", "Emby.Server.Implementations\Emby.Server.Implementations.csproj", "{E383961B-9356-4D5D-8233-9A1079D03055}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{41093F42-C7CC-4D07-956B-6182CBEDE2EC}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + Directory.Packages.props = Directory.Packages.props + SharedVersion.cs = SharedVersion.cs + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "src\Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Api", "Jellyfin.Api\Jellyfin.Api.csproj", "{DFBEFB4C-DA19-4143-98B7-27320C7F7163}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Common.Tests", "tests\Jellyfin.Common.Tests\Jellyfin.Common.Tests.csproj", "{DF194677-DFD3-42AF-9F75-D44D5A416478}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.MediaEncoding.Tests", "tests\Jellyfin.MediaEncoding.Tests\Jellyfin.MediaEncoding.Tests.csproj", "{28464062-0939-4AA7-9F7B-24DDDA61A7C0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Naming.Tests", "tests\Jellyfin.Naming.Tests\Jellyfin.Naming.Tests.csproj", "{3998657B-1CCC-49DD-A19F-275DC8495F57}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Api.Tests", "tests\Jellyfin.Api.Tests\Jellyfin.Api.Tests.csproj", "{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementations.Tests", "tests\Jellyfin.Server.Implementations.Tests\Jellyfin.Server.Implementations.Tests.csproj", "{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Controller.Tests", "tests\Jellyfin.Controller.Tests\Jellyfin.Controller.Tests.csproj", "{462584F7-5023-4019-9EAC-B98CA458C0A0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Data", "Jellyfin.Data\Jellyfin.Data.csproj", "{F03299F2-469F-40EF-A655-3766F97A5702}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementations", "Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj", "{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking", "src\Jellyfin.Networking\Jellyfin.Networking.csproj", "{0A3FCC4D-C714-4072-B90F-E374A15F9FF9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Providers.Tests", "tests\Jellyfin.Providers.Tests\Jellyfin.Providers.Tests.csproj", "{A964008C-2136-4716-B6CB-B3426C22320A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Extensions", "src\Jellyfin.Extensions\Jellyfin.Extensions.csproj", "{750B8757-BE3D-4F8C-941A-FBAD94904ADA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Extensions.Tests", "tests\Jellyfin.Extensions.Tests\Jellyfin.Extensions.Tests.csproj", "{332A5C7A-F907-47CA-910E-BE6F7371B9E0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.MediaEncoding.Keyframes", "src\Jellyfin.MediaEncoding.Keyframes\Jellyfin.MediaEncoding.Keyframes.csproj", "{06535CA1-4097-4360-85EB-5FB875D53239}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.MediaEncoding.Hls", "src\Jellyfin.MediaEncoding.Hls\Jellyfin.MediaEncoding.Hls.csproj", "{DA9FD356-4894-4830-B208-D6BCE3E65B11}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.MediaEncoding.Hls.Tests", "tests\Jellyfin.MediaEncoding.Hls.Tests\Jellyfin.MediaEncoding.Hls.Tests.csproj", "{FE47334C-EFDE-4519-BD50-F24430FF360B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.MediaEncoding.Keyframes.Tests", "tests\Jellyfin.MediaEncoding.Keyframes.Tests\Jellyfin.MediaEncoding.Keyframes.Tests.csproj", "{24960660-DE6C-47BF-AEEF-CEE8F19FE6C2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.LiveTv.Tests", "tests\Jellyfin.LiveTv.Tests\Jellyfin.LiveTv.Tests.csproj", "{C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.LiveTv", "src\Jellyfin.LiveTv\Jellyfin.LiveTv.csproj", "{8C6B2B13-58A4-4506-9DAB-1F882A093FE0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Jellyfin.Database", "Jellyfin.Database", "{4C54CE05-69C8-48FA-8785-39F7F6DB1CAD}" + ProjectSection(SolutionItems) = preProject + src\Jellyfin.Database\readme.md = src\Jellyfin.Database\readme.md + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Database.Providers.Sqlite", "src\Jellyfin.Database\Jellyfin.Database.Providers.Sqlite\Jellyfin.Database.Providers.Sqlite.csproj", "{A5590358-33CC-4B39-BDE7-DC62FEB03C76}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Database.Implementations", "src\Jellyfin.Database\Jellyfin.Database.Implementations\Jellyfin.Database.Implementations.csproj", "{8C9F9221-8415-496C-B1F5-E7756F03FA59}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.CodeAnalysis", "src\Jellyfin.CodeAnalysis\Jellyfin.CodeAnalysis.csproj", "{11643D0F-6761-4EF7-AB71-6F9F8DE00714}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.Build.0 = Release|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.Build.0 = Release|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.Build.0 = Release|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.Build.0 = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.Build.0 = Release|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.Build.0 = Release|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.Build.0 = Release|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.Build.0 = Release|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.Build.0 = Release|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.Build.0 = Release|Any CPU + {DFBEFB4C-DA19-4143-98B7-27320C7F7163}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DFBEFB4C-DA19-4143-98B7-27320C7F7163}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DFBEFB4C-DA19-4143-98B7-27320C7F7163}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DFBEFB4C-DA19-4143-98B7-27320C7F7163}.Release|Any CPU.Build.0 = Release|Any CPU + {DF194677-DFD3-42AF-9F75-D44D5A416478}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF194677-DFD3-42AF-9F75-D44D5A416478}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF194677-DFD3-42AF-9F75-D44D5A416478}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF194677-DFD3-42AF-9F75-D44D5A416478}.Release|Any CPU.Build.0 = Release|Any CPU + {28464062-0939-4AA7-9F7B-24DDDA61A7C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28464062-0939-4AA7-9F7B-24DDDA61A7C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28464062-0939-4AA7-9F7B-24DDDA61A7C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28464062-0939-4AA7-9F7B-24DDDA61A7C0}.Release|Any CPU.Build.0 = Release|Any CPU + {3998657B-1CCC-49DD-A19F-275DC8495F57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3998657B-1CCC-49DD-A19F-275DC8495F57}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3998657B-1CCC-49DD-A19F-275DC8495F57}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3998657B-1CCC-49DD-A19F-275DC8495F57}.Release|Any CPU.Build.0 = Release|Any CPU + {A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}.Release|Any CPU.Build.0 = Release|Any CPU + {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}.Release|Any CPU.Build.0 = Release|Any CPU + {462584F7-5023-4019-9EAC-B98CA458C0A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {462584F7-5023-4019-9EAC-B98CA458C0A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {462584F7-5023-4019-9EAC-B98CA458C0A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {462584F7-5023-4019-9EAC-B98CA458C0A0}.Release|Any CPU.Build.0 = Release|Any CPU + {F03299F2-469F-40EF-A655-3766F97A5702}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F03299F2-469F-40EF-A655-3766F97A5702}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F03299F2-469F-40EF-A655-3766F97A5702}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F03299F2-469F-40EF-A655-3766F97A5702}.Release|Any CPU.Build.0 = Release|Any CPU + {DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Release|Any CPU.Build.0 = Release|Any CPU + {0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Release|Any CPU.Build.0 = Release|Any CPU + {30922383-D513-4F4D-B890-A940B57FA353}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {30922383-D513-4F4D-B890-A940B57FA353}.Debug|Any CPU.Build.0 = Debug|Any CPU + {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.ActiveCfg = Release|Any CPU + {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.Build.0 = Release|Any CPU + {FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Release|Any CPU.Build.0 = Release|Any CPU + {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU + {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.Build.0 = Release|Any CPU + {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.Build.0 = Release|Any CPU + {A964008C-2136-4716-B6CB-B3426C22320A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A964008C-2136-4716-B6CB-B3426C22320A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A964008C-2136-4716-B6CB-B3426C22320A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A964008C-2136-4716-B6CB-B3426C22320A}.Release|Any CPU.Build.0 = Release|Any CPU + {750B8757-BE3D-4F8C-941A-FBAD94904ADA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {750B8757-BE3D-4F8C-941A-FBAD94904ADA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {750B8757-BE3D-4F8C-941A-FBAD94904ADA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {750B8757-BE3D-4F8C-941A-FBAD94904ADA}.Release|Any CPU.Build.0 = Release|Any CPU + {332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Release|Any CPU.Build.0 = Release|Any CPU + {06535CA1-4097-4360-85EB-5FB875D53239}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {06535CA1-4097-4360-85EB-5FB875D53239}.Debug|Any CPU.Build.0 = Debug|Any CPU + {06535CA1-4097-4360-85EB-5FB875D53239}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {06535CA1-4097-4360-85EB-5FB875D53239}.Release|Any CPU.Build.0 = Debug|Any CPU + {DA9FD356-4894-4830-B208-D6BCE3E65B11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA9FD356-4894-4830-B208-D6BCE3E65B11}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA9FD356-4894-4830-B208-D6BCE3E65B11}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {DA9FD356-4894-4830-B208-D6BCE3E65B11}.Release|Any CPU.Build.0 = Debug|Any CPU + {FE47334C-EFDE-4519-BD50-F24430FF360B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE47334C-EFDE-4519-BD50-F24430FF360B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE47334C-EFDE-4519-BD50-F24430FF360B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE47334C-EFDE-4519-BD50-F24430FF360B}.Release|Any CPU.Build.0 = Release|Any CPU + {24960660-DE6C-47BF-AEEF-CEE8F19FE6C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24960660-DE6C-47BF-AEEF-CEE8F19FE6C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24960660-DE6C-47BF-AEEF-CEE8F19FE6C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24960660-DE6C-47BF-AEEF-CEE8F19FE6C2}.Release|Any CPU.Build.0 = Release|Any CPU + {C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3}.Release|Any CPU.Build.0 = Release|Any CPU + {8C6B2B13-58A4-4506-9DAB-1F882A093FE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C6B2B13-58A4-4506-9DAB-1F882A093FE0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C6B2B13-58A4-4506-9DAB-1F882A093FE0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C6B2B13-58A4-4506-9DAB-1F882A093FE0}.Release|Any CPU.Build.0 = Release|Any CPU + {A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Release|Any CPU.Build.0 = Release|Any CPU + {8C9F9221-8415-496C-B1F5-E7756F03FA59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C9F9221-8415-496C-B1F5-E7756F03FA59}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C9F9221-8415-496C-B1F5-E7756F03FA59}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C9F9221-8415-496C-B1F5-E7756F03FA59}.Release|Any CPU.Build.0 = Release|Any CPU + {11643D0F-6761-4EF7-AB71-6F9F8DE00714}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11643D0F-6761-4EF7-AB71-6F9F8DE00714}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11643D0F-6761-4EF7-AB71-6F9F8DE00714}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11643D0F-6761-4EF7-AB71-6F9F8DE00714}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {08FFF49B-F175-4807-A2B5-73B0EBD9F716} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C} + {154872D9-6C12-4007-96E3-8F70A58386CE} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C} + {DF194677-DFD3-42AF-9F75-D44D5A416478} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {28464062-0939-4AA7-9F7B-24DDDA61A7C0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {3998657B-1CCC-49DD-A19F-275DC8495F57} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {0A3FCC4D-C714-4072-B90F-E374A15F9FF9} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C} + {30922383-D513-4F4D-B890-A940B57FA353} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {A964008C-2136-4716-B6CB-B3426C22320A} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {750B8757-BE3D-4F8C-941A-FBAD94904ADA} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C} + {332A5C7A-F907-47CA-910E-BE6F7371B9E0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {06535CA1-4097-4360-85EB-5FB875D53239} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C} + {DA9FD356-4894-4830-B208-D6BCE3E65B11} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C} + {FE47334C-EFDE-4519-BD50-F24430FF360B} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {24960660-DE6C-47BF-AEEF-CEE8F19FE6C2} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {8C6B2B13-58A4-4506-9DAB-1F882A093FE0} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C} + {4C54CE05-69C8-48FA-8785-39F7F6DB1CAD} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C} + {A5590358-33CC-4B39-BDE7-DC62FEB03C76} = {4C54CE05-69C8-48FA-8785-39F7F6DB1CAD} + {8C9F9221-8415-496C-B1F5-E7756F03FA59} = {4C54CE05-69C8-48FA-8785-39F7F6DB1CAD} + {11643D0F-6761-4EF7-AB71-6F9F8DE00714} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} + EndGlobalSection + GlobalSection(AutomaticVersions) = postSolution + UpdateAssemblyVersion = True + UpdateAssemblyFileVersion = True + UpdateAssemblyInfoVersion = True + AssemblyVersionSettings = None.None.None.None + AssemblyFileVersionSettings = None.None.None.None + AssemblyInfoVersionSettings = None.None.None.None + UpdatePackageVersion = False + AssemblyInfoVersionType = SettingsVersion + InheritWinAppVersionFrom = None + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.StandardHeader = $1 + $1.IncludeInNewFiles = False + $0.DotNetNamingPolicy = $2 + $2.DirectoryNamespaceAssociation = PrefixedHierarchical + EndGlobalSection +EndGlobal diff --git a/Jellyfin.sln.DotSettings b/Jellyfin.sln.DotSettings new file mode 100644 index 00000000..2ef03748 --- /dev/null +++ b/Jellyfin.sln.DotSettings @@ -0,0 +1,4 @@ + + True + True + True \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..4522ba06 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {{description}} + Copyright (C) {{year}} {{fullname}} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/MediaBrowser.Common/Api/Policies.cs b/MediaBrowser.Common/Api/Policies.cs new file mode 100644 index 00000000..435f4798 --- /dev/null +++ b/MediaBrowser.Common/Api/Policies.cs @@ -0,0 +1,97 @@ +namespace MediaBrowser.Common.Api; + +/// +/// Policies for the API authorization. +/// +public static class Policies +{ + /// + /// Policy name for requiring first time setup or elevated privileges. + /// + public const string FirstTimeSetupOrElevated = "FirstTimeSetupOrElevated"; + + /// + /// Policy name for requiring elevated privileges. + /// + public const string RequiresElevation = "RequiresElevation"; + + /// + /// Policy name for allowing local access only. + /// + public const string LocalAccessOnly = "LocalAccessOnly"; + + /// + /// Policy name for escaping schedule controls. + /// + public const string IgnoreParentalControl = "IgnoreParentalControl"; + + /// + /// Policy name for requiring download permission. + /// + public const string Download = "Download"; + + /// + /// Policy name for requiring first time setup or default permissions. + /// + public const string FirstTimeSetupOrDefault = "FirstTimeSetupOrDefault"; + + /// + /// Policy name for requiring local access or elevated privileges. + /// + public const string LocalAccessOrRequiresElevation = "LocalAccessOrRequiresElevation"; + + /// + /// Policy name for requiring (anonymous) LAN access. + /// + public const string AnonymousLanAccessPolicy = "AnonymousLanAccessPolicy"; + + /// + /// Policy name for escaping schedule controls or requiring first time setup. + /// + public const string FirstTimeSetupOrIgnoreParentalControl = "FirstTimeSetupOrIgnoreParentalControl"; + + /// + /// Policy name for accessing SyncPlay. + /// + public const string SyncPlayHasAccess = "SyncPlayHasAccess"; + + /// + /// Policy name for creating a SyncPlay group. + /// + public const string SyncPlayCreateGroup = "SyncPlayCreateGroup"; + + /// + /// Policy name for joining a SyncPlay group. + /// + public const string SyncPlayJoinGroup = "SyncPlayJoinGroup"; + + /// + /// Policy name for accessing a SyncPlay group. + /// + public const string SyncPlayIsInGroup = "SyncPlayIsInGroup"; + + /// + /// Policy name for accessing collection management. + /// + public const string CollectionManagement = "CollectionManagement"; + + /// + /// Policy name for accessing LiveTV. + /// + public const string LiveTvAccess = "LiveTvAccess"; + + /// + /// Policy name for managing LiveTV. + /// + public const string LiveTvManagement = "LiveTvManagement"; + + /// + /// Policy name for accessing subtitles management. + /// + public const string SubtitleManagement = "SubtitleManagement"; + + /// + /// Policy name for accessing lyric management. + /// + public const string LyricManagement = "LyricManagement"; +} diff --git a/MediaBrowser.Common/Configuration/ConfigurationStore.cs b/MediaBrowser.Common/Configuration/ConfigurationStore.cs new file mode 100644 index 00000000..050ab1ab --- /dev/null +++ b/MediaBrowser.Common/Configuration/ConfigurationStore.cs @@ -0,0 +1,22 @@ +#nullable disable + +using System; + +namespace MediaBrowser.Common.Configuration +{ + /// + /// Describes a single entry in the application configuration. + /// + public class ConfigurationStore + { + /// + /// Gets or sets the unique identifier for the configuration. + /// + public string Key { get; set; } + + /// + /// Gets or sets the type used to store the data for this configuration entry. + /// + public Type ConfigurationType { get; set; } + } +} diff --git a/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs b/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs new file mode 100644 index 00000000..90b1ff70 --- /dev/null +++ b/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs @@ -0,0 +1,33 @@ +using System; + +namespace MediaBrowser.Common.Configuration +{ + /// + /// for the ConfigurationUpdated event. + /// + public class ConfigurationUpdateEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The configuration key. + /// The new configuration. + public ConfigurationUpdateEventArgs(string key, object newConfiguration) + { + Key = key; + NewConfiguration = newConfiguration; + } + + /// + /// Gets the key. + /// + /// The key. + public string Key { get; } + + /// + /// Gets the new configuration. + /// + /// The new configuration. + public object NewConfiguration { get; } + } +} diff --git a/MediaBrowser.Common/Configuration/EncodingConfigurationExtensions.cs b/MediaBrowser.Common/Configuration/EncodingConfigurationExtensions.cs new file mode 100644 index 00000000..78e96ab4 --- /dev/null +++ b/MediaBrowser.Common/Configuration/EncodingConfigurationExtensions.cs @@ -0,0 +1,42 @@ +using System; +using System.IO; +using MediaBrowser.Model.Configuration; + +namespace MediaBrowser.Common.Configuration +{ + /// + /// Class containing extension methods for working with the encoding configuration. + /// + public static class EncodingConfigurationExtensions + { + /// + /// Gets the encoding options. + /// + /// The configuration manager. + /// The encoding options. + public static EncodingOptions GetEncodingOptions(this IConfigurationManager configurationManager) + => configurationManager.GetConfiguration("encoding"); + + /// + /// Retrieves the transcoding temp path from the encoding configuration, falling back to a default if no path + /// is specified in configuration. If the directory does not exist, it will be created. + /// + /// The configuration manager. + /// The transcoding temp path. + /// If the directory does not exist, and the caller does not have the required permission to create it. + /// If there is a custom path transcoding path specified, but it is invalid. + /// If the directory does not exist, and it also could not be created. + public static string GetTranscodePath(this IConfigurationManager configurationManager) + { + // Get the configured path and fall back to a default + var transcodingTempPath = configurationManager.GetEncodingOptions().TranscodingTempPath; + if (string.IsNullOrEmpty(transcodingTempPath)) + { + transcodingTempPath = Path.Combine(configurationManager.CommonApplicationPaths.CachePath, "transcodes"); + } + + configurationManager.CommonApplicationPaths.CreateAndCheckMarker(transcodingTempPath, "transcode", true); + return transcodingTempPath; + } + } +} diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs new file mode 100644 index 00000000..3a619749 --- /dev/null +++ b/MediaBrowser.Common/Configuration/IApplicationPaths.cs @@ -0,0 +1,113 @@ +namespace MediaBrowser.Common.Configuration +{ + /// + /// Interface IApplicationPaths. + /// + public interface IApplicationPaths + { + /// + /// Gets the path to the program data folder. + /// + /// The program data path. + string ProgramDataPath { get; } + + /// + /// Gets the path to the web UI resources folder. + /// + /// + /// This value is not relevant if the server is configured to not host any static web content. + /// + string WebPath { get; } + + /// + /// Gets the path to the program system folder. + /// + /// The program data path. + string ProgramSystemPath { get; } + + /// + /// Gets the folder path to the data directory. + /// + /// The data directory. + string DataPath { get; } + + /// + /// Gets the image cache path. + /// + /// The image cache path. + string ImageCachePath { get; } + + /// + /// Gets the path to the plugin directory. + /// + /// The plugins path. + string PluginsPath { get; } + + /// + /// Gets the path to the plugin configurations directory. + /// + /// The plugin configurations path. + string PluginConfigurationsPath { get; } + + /// + /// Gets the path to the log directory. + /// + /// The log directory path. + string LogDirectoryPath { get; } + + /// + /// Gets the path to the application configuration root directory. + /// + /// The configuration directory path. + string ConfigurationDirectoryPath { get; } + + /// + /// Gets the path to the system configuration file. + /// + /// The system configuration file path. + string SystemConfigurationFilePath { get; } + + /// + /// Gets the folder path to the cache directory. + /// + /// The cache directory. + string CachePath { get; } + + /// + /// Gets the folder path to the temp directory within the cache folder. + /// + /// The temp directory. + string TempDirectory { get; } + + /// + /// Gets the magic string used for virtual path manipulation. + /// + /// The magic string used for virtual path manipulation. + string VirtualDataPath { get; } + + /// + /// Gets the path used for storing trickplay files. + /// + /// The trickplay path. + string TrickplayPath { get; } + + /// + /// Gets the path used for storing backup archives. + /// + /// The backup path. + string BackupPath { get; } + + /// + /// Checks and creates all known base paths. + /// + void MakeSanityCheckOrThrow(); + + /// + /// Checks and creates the given path and adds it with a marker file if non existent. + /// + /// The path to check. + /// The common marker file name. + /// Check for other settings paths recursively. + void CreateAndCheckMarker(string path, string markerName, bool recursive = false); + } +} diff --git a/MediaBrowser.Common/Configuration/IConfigurationFactory.cs b/MediaBrowser.Common/Configuration/IConfigurationFactory.cs new file mode 100644 index 00000000..6db1f136 --- /dev/null +++ b/MediaBrowser.Common/Configuration/IConfigurationFactory.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Common.Configuration +{ + /// + /// Provides an interface to retrieve a configuration store. Classes with this interface are scanned for at + /// application start to dynamically register configuration for various modules/plugins. + /// + public interface IConfigurationFactory + { + /// + /// Get the configuration store for this module. + /// + /// The configuration store. + IEnumerable GetConfigurations(); + } +} diff --git a/MediaBrowser.Common/Configuration/IConfigurationManager.cs b/MediaBrowser.Common/Configuration/IConfigurationManager.cs new file mode 100644 index 00000000..18a8d3e7 --- /dev/null +++ b/MediaBrowser.Common/Configuration/IConfigurationManager.cs @@ -0,0 +1,97 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using MediaBrowser.Model.Configuration; + +namespace MediaBrowser.Common.Configuration +{ + public interface IConfigurationManager + { + /// + /// Occurs when [configuration updating]. + /// + event EventHandler NamedConfigurationUpdating; + + /// + /// Occurs when [configuration updated]. + /// + event EventHandler ConfigurationUpdated; + + /// + /// Occurs when [named configuration updated]. + /// + event EventHandler NamedConfigurationUpdated; + + /// + /// Gets the application paths. + /// + /// The application paths. + IApplicationPaths CommonApplicationPaths { get; } + + /// + /// Gets the configuration. + /// + /// The configuration. + BaseApplicationConfiguration CommonConfiguration { get; } + + /// + /// Saves the configuration. + /// + void SaveConfiguration(); + + /// + /// Replaces the configuration. + /// + /// The new configuration. + void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration); + + /// + /// Manually pre-loads a factory so that it is available pre system initialisation. + /// + /// Class to register. + void RegisterConfiguration() + where T : IConfigurationFactory; + + /// + /// Gets the configuration. + /// + /// The key. + /// System.Object. + object GetConfiguration(string key); + + /// + /// Gets the array of configuration stores. + /// + /// Array of ConfigurationStore. + ConfigurationStore[] GetConfigurationStores(); + + /// + /// Gets the type of the configuration. + /// + /// The key. + /// Type. + Type GetConfigurationType(string key); + + /// + /// Saves the configuration. + /// + /// The key. + /// The configuration. + void SaveConfiguration(string key, object configuration); + + /// + /// Adds the parts. + /// + /// The factories. + void AddParts(IEnumerable factories); + } + + public static class ConfigurationManagerExtensions + { + public static T GetConfiguration(this IConfigurationManager manager, string key) + { + return (T)manager.GetConfiguration(key); + } + } +} diff --git a/MediaBrowser.Common/Configuration/IValidatingConfiguration.cs b/MediaBrowser.Common/Configuration/IValidatingConfiguration.cs new file mode 100644 index 00000000..3b1d84f3 --- /dev/null +++ b/MediaBrowser.Common/Configuration/IValidatingConfiguration.cs @@ -0,0 +1,15 @@ +namespace MediaBrowser.Common.Configuration +{ + /// + /// A configuration store that can be validated. + /// + public interface IValidatingConfiguration + { + /// + /// Validation method to be invoked before saving the configuration. + /// + /// The old configuration. + /// The new configuration. + void Validate(object oldConfig, object newConfig); + } +} diff --git a/MediaBrowser.Common/Crc32.cs b/MediaBrowser.Common/Crc32.cs new file mode 100644 index 00000000..599eb4c9 --- /dev/null +++ b/MediaBrowser.Common/Crc32.cs @@ -0,0 +1,89 @@ +#pragma warning disable CS1591 + +using System; + +namespace MediaBrowser.Common +{ + public static class Crc32 + { + private static readonly uint[] _crcTable = + { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, + 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, + 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, + 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, + 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, + 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, + 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, + 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, + 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, + 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, + 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, + 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, + 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, + 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, + 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, + 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + }; + + public static uint Compute(ReadOnlySpan bytes) + { + var crc = 0xffffffff; + var len = bytes.Length; + for (var i = 0; i < len; i++) + { + crc = (crc >> 8) ^ _crcTable[(bytes[i] ^ crc) & 0xff]; + } + + return ~crc; + } + } +} diff --git a/MediaBrowser.Common/Events/EventHelper.cs b/MediaBrowser.Common/Events/EventHelper.cs new file mode 100644 index 00000000..0e495f4f --- /dev/null +++ b/MediaBrowser.Common/Events/EventHelper.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Common.Events +{ + /// + /// Class EventHelper. + /// + // TODO: @bond Remove + public static class EventHelper + { + /// + /// Fires the event. + /// + /// The handler. + /// The sender. + /// The instance containing the event data. + /// The logger. + public static void QueueEventIfNotNull(EventHandler? handler, object sender, EventArgs args, ILogger logger) + { + if (handler is not null) + { + Task.Run(() => + { + try + { + handler(sender, args); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in event handler"); + } + }); + } + } + + /// + /// Queues the event. + /// + /// Argument type for the handler. + /// The handler. + /// The sender. + /// The args. + /// The logger. + public static void QueueEventIfNotNull(EventHandler? handler, object sender, T args, ILogger logger) + { + if (handler is not null) + { + Task.Run(() => + { + try + { + handler(sender, args); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in event handler"); + } + }); + } + } + } +} diff --git a/MediaBrowser.Common/Extensions/BaseExtensions.cs b/MediaBrowser.Common/Extensions/BaseExtensions.cs new file mode 100644 index 00000000..3615b662 --- /dev/null +++ b/MediaBrowser.Common/Extensions/BaseExtensions.cs @@ -0,0 +1,37 @@ +using System; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; + +namespace MediaBrowser.Common.Extensions +{ + /// + /// Class BaseExtensions. + /// + public static partial class BaseExtensions + { + // http://stackoverflow.com/questions/1349023/how-can-i-strip-html-from-text-in-net + [GeneratedRegex(@"<(.|\n)*?>")] + private static partial Regex StripHtmlRegex(); + + /// + /// Strips the HTML. + /// + /// The HTML string. + /// . + public static string StripHtml(this string htmlString) + => StripHtmlRegex().Replace(htmlString, string.Empty).Trim(); + + /// + /// Gets the Md5. + /// + /// The string. + /// . + public static Guid GetMD5(this string str) + { +#pragma warning disable CA5351 + return new Guid(MD5.HashData(Encoding.Unicode.GetBytes(str))); +#pragma warning restore CA5351 + } + } +} diff --git a/MediaBrowser.Common/Extensions/HttpContextExtensions.cs b/MediaBrowser.Common/Extensions/HttpContextExtensions.cs new file mode 100644 index 00000000..739a53c7 --- /dev/null +++ b/MediaBrowser.Common/Extensions/HttpContextExtensions.cs @@ -0,0 +1,41 @@ +using System.Net; +using Microsoft.AspNetCore.Http; + +namespace MediaBrowser.Common.Extensions +{ + /// + /// Static class containing extension methods for . + /// + public static class HttpContextExtensions + { + /// + /// Checks the origin of the HTTP context. + /// + /// The incoming HTTP context. + /// true if the request is coming from the same machine as is running the server, false otherwise. + public static bool IsLocal(this HttpContext context) + { + return (context.Connection.LocalIpAddress is null + && context.Connection.RemoteIpAddress is null) + || Equals(context.Connection.LocalIpAddress, context.Connection.RemoteIpAddress); + } + + /// + /// Extracts the remote IP address of the caller of the HTTP context. + /// + /// The HTTP context. + /// The remote caller IP address. + public static IPAddress GetNormalizedRemoteIP(this HttpContext context) + { + // Default to the loopback address if no RemoteIpAddress is specified (i.e. during integration tests) + var ip = context.Connection.RemoteIpAddress ?? IPAddress.Loopback; + + if (ip.IsIPv4MappedToIPv6) + { + ip = ip.MapToIPv4(); + } + + return ip; + } + } +} diff --git a/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs b/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs new file mode 100644 index 00000000..48e758ee --- /dev/null +++ b/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs @@ -0,0 +1,26 @@ +using System; + +namespace MediaBrowser.Common.Extensions +{ + /// + /// Class MethodNotAllowedException. + /// + public class MethodNotAllowedException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public MethodNotAllowedException() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message. + public MethodNotAllowedException(string message) + : base(message) + { + } + } +} diff --git a/MediaBrowser.Common/Extensions/ProcessExtensions.cs b/MediaBrowser.Common/Extensions/ProcessExtensions.cs new file mode 100644 index 00000000..bb8ab130 --- /dev/null +++ b/MediaBrowser.Common/Extensions/ProcessExtensions.cs @@ -0,0 +1,28 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Common.Extensions +{ + /// + /// Extension methods for . + /// + public static class ProcessExtensions + { + /// + /// Asynchronously wait for the process to exit. + /// + /// The process to wait for. + /// The duration to wait before cancelling waiting for the task. + /// A task that will complete when the process has exited, cancellation has been requested, or an error occurs. + /// The timeout ended. + public static async Task WaitForExitAsync(this Process process, TimeSpan timeout) + { + using (var cancelTokenSource = new CancellationTokenSource(timeout)) + { + await process.WaitForExitAsync(cancelTokenSource.Token).ConfigureAwait(false); + } + } + } +} diff --git a/MediaBrowser.Common/Extensions/RateLimitExceededException.cs b/MediaBrowser.Common/Extensions/RateLimitExceededException.cs new file mode 100644 index 00000000..95802a46 --- /dev/null +++ b/MediaBrowser.Common/Extensions/RateLimitExceededException.cs @@ -0,0 +1,25 @@ +#pragma warning disable CS1591 + +using System; + +namespace MediaBrowser.Common.Extensions +{ + public class RateLimitExceededException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public RateLimitExceededException() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message. + public RateLimitExceededException(string message) + : base(message) + { + } + } +} diff --git a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs b/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs new file mode 100644 index 00000000..22130c5a --- /dev/null +++ b/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs @@ -0,0 +1,26 @@ +using System; + +namespace MediaBrowser.Common.Extensions +{ + /// + /// Class ResourceNotFoundException. + /// + public class ResourceNotFoundException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public ResourceNotFoundException() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message. + public ResourceNotFoundException(string message) + : base(message) + { + } + } +} diff --git a/MediaBrowser.Common/FfmpegException.cs b/MediaBrowser.Common/FfmpegException.cs new file mode 100644 index 00000000..be420196 --- /dev/null +++ b/MediaBrowser.Common/FfmpegException.cs @@ -0,0 +1,39 @@ +using System; + +namespace MediaBrowser.Common +{ + /// + /// Represents errors that occur during interaction with FFmpeg. + /// + public class FfmpegException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public FfmpegException() + { + } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public FfmpegException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error message and a + /// reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if + /// no inner exception is specified. + /// + public FfmpegException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs new file mode 100644 index 00000000..23795c6b --- /dev/null +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; + +namespace MediaBrowser.Common +{ + /// + /// Delegate used with GetExports{T}. + /// + /// Type to create. + /// New instance of type type. + public delegate object? CreationDelegateFactory(Type type); + + /// + /// An interface to be implemented by the applications hosting a kernel. + /// + public interface IApplicationHost + { + /// + /// Occurs when [has pending restart changed]. + /// + event EventHandler? HasPendingRestartChanged; + + /// + /// Gets the name. + /// + /// The name. + string Name { get; } + + /// + /// Gets the device identifier. + /// + /// The device identifier. + string SystemId { get; } + + /// + /// Gets a value indicating whether this instance has pending changes requiring a restart. + /// + /// true if this instance has a pending restart; otherwise, false. + bool HasPendingRestart { get; } + + /// + /// Gets or sets a value indicating whether the application should restart. + /// + bool ShouldRestart { get; set; } + + /// + /// Gets the application version. + /// + /// The application version. + Version ApplicationVersion { get; } + + /// + /// Gets or sets the service provider. + /// + IServiceProvider? ServiceProvider { get; set; } + + /// + /// Gets the application version. + /// + /// The application version. + string ApplicationVersionString { get; } + + /// + /// Gets the application user agent. + /// + /// The application user agent. + string ApplicationUserAgent { get; } + + /// + /// Gets the email address for use within a comment section of a user agent field. + /// Presently used to provide contact information to MusicBrainz service. + /// + string ApplicationUserAgentAddress { get; } + + /// + /// Gets all plugin assemblies which implement a custom rest api. + /// + /// An containing the plugin assemblies. + IEnumerable GetApiPluginAssemblies(); + + /// + /// Notifies the pending restart. + /// + void NotifyPendingRestart(); + + /// + /// Gets the exports. + /// + /// The type. + /// If set to true [manage lifetime]. + /// . + IReadOnlyCollection GetExports(bool manageLifetime = true); + + /// + /// Gets the exports. + /// + /// The type. + /// Delegate function that gets called to create the object. + /// If set to true [manage lifetime]. + /// . + IReadOnlyCollection GetExports(CreationDelegateFactory defaultFunc, bool manageLifetime = true); + + /// + /// Gets the export types. + /// + /// The type. + /// IEnumerable{Type}. + IEnumerable GetExportTypes(); + + /// + /// Resolves this instance. + /// + /// The Type. + /// ``0. + T Resolve(); + + /// + /// Initializes this instance. + /// + /// Instance of the interface. + void Init(IServiceCollection serviceCollection); + } +} diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj new file mode 100644 index 00000000..c128c2b6 --- /dev/null +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -0,0 +1,61 @@ + + + + + {9142EEFA-7570-41E1-BFCC-468BB571AF2F} + + + + Jellyfin Contributors + Jellyfin.Common + 10.12.0 + https://github.com/jellyfin/jellyfin + GPL-3.0-only + + + + + + + + + + + + + net10.0 + false + true + true + true + true + snupkg + + + + + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + + + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + <_Parameter1>Jellyfin.Common.Tests + + + + diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs new file mode 100644 index 00000000..bd785bcb --- /dev/null +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Net; +using System.Net.NetworkInformation; +using MediaBrowser.Model.Net; +using Microsoft.AspNetCore.Http; + +namespace MediaBrowser.Common.Net +{ + /// + /// Interface for the NetworkManager class. + /// + public interface INetworkManager + { + /// + /// Event triggered on network changes. + /// + event EventHandler NetworkChanged; + + /// + /// Gets a value indicating whether IPv4 is enabled. + /// + bool IsIPv4Enabled { get; } + + /// + /// Gets a value indicating whether IPv6 is enabled. + /// + bool IsIPv6Enabled { get; } + + /// + /// Calculates the list of interfaces to use for Kestrel. + /// + /// A IReadOnlyList{IPData} object containing all the interfaces to bind. + /// If all the interfaces are specified, and none are excluded, it returns zero items + /// to represent any address. + /// When false, return or for all interfaces. + IReadOnlyList GetAllBindInterfaces(bool individualInterfaces = false); + + /// + /// Returns a list containing the loopback interfaces. + /// + /// IReadOnlyList{IPData}. + IReadOnlyList GetLoopbacks(); + + /// + /// Retrieves the bind address to use in system URLs. (Server Discovery, PlayTo, LiveTV, SystemInfo) + /// If no bind addresses are specified, an internal interface address is selected. + /// The priority of selection is as follows:- + /// + /// The value contained in the startup parameter --published-server-url. + /// + /// If the user specified custom subnet overrides, the correct subnet for the source address. + /// + /// If the user specified bind interfaces to use:- + /// The bind interface that contains the source subnet. + /// The first bind interface specified that suits best first the source's endpoint. eg. external or internal. + /// + /// If the source is from a public subnet address range and the user hasn't specified any bind addresses:- + /// The first public interface that isn't a loopback and contains the source subnet. + /// The first public interface that isn't a loopback. + /// The first internal interface that isn't a loopback. + /// + /// If the source is from a private subnet address range and the user hasn't specified any bind addresses:- + /// The first private interface that contains the source subnet. + /// The first private interface that isn't a loopback. + /// + /// If no interfaces meet any of these criteria, then a loopback address is returned. + /// + /// Interfaces that have been specifically excluded from binding are not used in any of the calculations. + /// + /// Source of the request. + /// Optional port returned, if it's part of an override. + /// IP address to use, or loopback address if all else fails. + string GetBindAddress(HttpRequest source, out int? port); + + /// + /// Retrieves the bind address to use in system URLs. (Server Discovery, PlayTo, LiveTV, SystemInfo) + /// If no bind addresses are specified, an internal interface address is selected. + /// + /// IP address of the request. + /// Optional port returned, if it's part of an override. + /// Optional boolean denoting if published server overrides should be ignored. Defaults to false. + /// IP address to use, or loopback address if all else fails. + string GetBindAddress(IPAddress? source, out int? port, bool skipOverrides = false); + + /// + /// Retrieves the bind address to use in system URLs. (Server Discovery, PlayTo, LiveTV, SystemInfo) + /// If no bind addresses are specified, an internal interface address is selected. + /// (See . + /// + /// Source of the request. + /// Optional port returned, if it's part of an override. + /// IP address to use, or loopback address if all else fails. + string GetBindAddress(string source, out int? port); + + /// + /// Returns true if the address is part of the user defined LAN. + /// + /// IP to check. + /// True if endpoint is within the LAN range. + bool IsInLocalNetwork(string address); + + /// + /// Returns true if the address is part of the user defined LAN. + /// + /// IP to check. + /// True if endpoint is within the LAN range. + bool IsInLocalNetwork(IPAddress address); + + /// + /// Attempts to convert the interface name to an IP address. + /// eg. "eth1", or "enp3s5". + /// + /// Interface name. + /// Resulting object's IP addresses, if successful. + /// Success of the operation. + bool TryParseInterface(string intf, [NotNullWhen(true)] out IReadOnlyList? result); + + /// + /// Returns all internal (LAN) bind interface addresses. + /// + /// An list of internal (LAN) interfaces addresses. + IReadOnlyList GetInternalBindAddresses(); + + /// + /// Checks if has access to the server. + /// + /// IP address of the client. + /// The result of evaluating the access policy, Allow if it should be allowed. + RemoteAccessPolicyResult ShouldAllowServerAccess(IPAddress remoteIP); + } +} diff --git a/MediaBrowser.Common/Net/NamedClient.cs b/MediaBrowser.Common/Net/NamedClient.cs new file mode 100644 index 00000000..9c5544b0 --- /dev/null +++ b/MediaBrowser.Common/Net/NamedClient.cs @@ -0,0 +1,28 @@ +namespace MediaBrowser.Common.Net +{ + /// + /// Registered http client names. + /// + public static class NamedClient + { + /// + /// Gets the value for the default named http client which implements happy eyeballs. + /// + public const string Default = nameof(Default); + + /// + /// Gets the value for the MusicBrainz named http client. + /// + public const string MusicBrainz = nameof(MusicBrainz); + + /// + /// Gets the value for the DLNA named http client. + /// + public const string Dlna = nameof(Dlna); + + /// + /// Non happy eyeballs implementation. + /// + public const string DirectIp = nameof(DirectIp); + } +} diff --git a/MediaBrowser.Common/Net/NetworkConfiguration.cs b/MediaBrowser.Common/Net/NetworkConfiguration.cs new file mode 100644 index 00000000..05335729 --- /dev/null +++ b/MediaBrowser.Common/Net/NetworkConfiguration.cs @@ -0,0 +1,176 @@ +#pragma warning disable CA1819 // Properties should not return arrays + +using System; + +namespace MediaBrowser.Common.Net; + +/// +/// Defines the . +/// +public class NetworkConfiguration +{ + /// + /// The default value for . + /// + public const int DefaultHttpPort = 8096; + + /// + /// The default value for and . + /// + public const int DefaultHttpsPort = 8920; + + private string _baseUrl = string.Empty; + + /// + /// Gets or sets a value used to specify the URL prefix that your Jellyfin instance can be accessed at. + /// + public string BaseUrl + { + get => _baseUrl; + set + { + // Normalize the start of the string + if (string.IsNullOrWhiteSpace(value)) + { + // If baseUrl is empty, set an empty prefix string + _baseUrl = string.Empty; + return; + } + + if (value[0] != '/') + { + // If baseUrl was not configured with a leading slash, append one for consistency + value = "/" + value; + } + + // Normalize the end of the string + if (value[^1] == '/') + { + // If baseUrl was configured with a trailing slash, remove it for consistency + value = value.Remove(value.Length - 1); + } + + _baseUrl = value; + } + } + + /// + /// Gets or sets a value indicating whether to use HTTPS. + /// + /// + /// In order for HTTPS to be used, in addition to setting this to true, valid values must also be + /// provided for and . + /// + public bool EnableHttps { get; set; } + + /// + /// Gets or sets a value indicating whether the server should force connections over HTTPS. + /// + public bool RequireHttps { get; set; } + + /// + /// Gets or sets the filesystem path of an X.509 certificate to use for SSL. + /// + public string CertificatePath { get; set; } = string.Empty; + + /// + /// Gets or sets the password required to access the X.509 certificate data in the file specified by . + /// + public string CertificatePassword { get; set; } = string.Empty; + + /// + /// Gets or sets the internal HTTP server port. + /// + /// The HTTP server port. + public int InternalHttpPort { get; set; } = DefaultHttpPort; + + /// + /// Gets or sets the internal HTTPS server port. + /// + /// The HTTPS server port. + public int InternalHttpsPort { get; set; } = DefaultHttpsPort; + + /// + /// Gets or sets the public HTTP port. + /// + /// The public HTTP port. + public int PublicHttpPort { get; set; } = DefaultHttpPort; + + /// + /// Gets or sets the public HTTPS port. + /// + /// The public HTTPS port. + public int PublicHttpsPort { get; set; } = DefaultHttpsPort; + + /// + /// Gets or sets a value indicating whether Autodiscovery is enabled. + /// + public bool AutoDiscovery { get; set; } = true; + + /// + /// Gets or sets a value indicating whether to enable automatic port forwarding. + /// + [Obsolete("No longer supported")] + public bool EnableUPnP { get; set; } + + /// + /// Gets or sets a value indicating whether IPv6 is enabled. + /// + public bool EnableIPv4 { get; set; } = true; + + /// + /// Gets or sets a value indicating whether IPv6 is enabled. + /// + public bool EnableIPv6 { get; set; } + + /// + /// Gets or sets a value indicating whether access from outside of the LAN is permitted. + /// + public bool EnableRemoteAccess { get; set; } = true; + + /// + /// Gets or sets the subnets that are deemed to make up the LAN. + /// + public string[] LocalNetworkSubnets { get; set; } = Array.Empty(); + + /// + /// Gets or sets the interface addresses which Jellyfin will bind to. If empty, all interfaces will be used. + /// + public string[] LocalNetworkAddresses { get; set; } = Array.Empty(); + + /// + /// Gets or sets the known proxies. + /// + public string[] KnownProxies { get; set; } = Array.Empty(); + + /// + /// Gets or sets a value indicating whether address names that match should be ignored for the purposes of binding. + /// + public bool IgnoreVirtualInterfaces { get; set; } = true; + + /// + /// Gets or sets a value indicating the interface name prefixes that should be ignored. The list can be comma separated and values are case-insensitive. . + /// + public string[] VirtualInterfaceNames { get; set; } = new string[] { "veth" }; + + /// + /// Gets or sets a value indicating whether the published server uri is based on information in HTTP requests. + /// + public bool EnablePublishedServerUriByRequest { get; set; } = false; + + /// + /// Gets or sets the PublishedServerUriBySubnet + /// Gets or sets PublishedServerUri to advertise for specific subnets. + /// + public string[] PublishedServerUriBySubnet { get; set; } = Array.Empty(); + + /// + /// Gets or sets the filter for remote IP connectivity. Used in conjunction with . + /// + public string[] RemoteIPFilter { get; set; } = Array.Empty(); + + /// + /// Gets or sets a value indicating whether contains a blacklist or a whitelist. Default is a whitelist. + /// + public bool IsRemoteIPFilterBlacklist { get; set; } +} diff --git a/MediaBrowser.Common/Net/NetworkConfigurationExtensions.cs b/MediaBrowser.Common/Net/NetworkConfigurationExtensions.cs new file mode 100644 index 00000000..9288964d --- /dev/null +++ b/MediaBrowser.Common/Net/NetworkConfigurationExtensions.cs @@ -0,0 +1,19 @@ +using MediaBrowser.Common.Configuration; + +namespace MediaBrowser.Common.Net; + +/// +/// Defines the . +/// +public static class NetworkConfigurationExtensions +{ + /// + /// Retrieves the network configuration. + /// + /// The . + /// The . + public static NetworkConfiguration GetNetworkConfiguration(this IConfigurationManager config) + { + return config.GetConfiguration(NetworkConfigurationStore.StoreKey); + } +} diff --git a/MediaBrowser.Common/Net/NetworkConfigurationFactory.cs b/MediaBrowser.Common/Net/NetworkConfigurationFactory.cs new file mode 100644 index 00000000..9309834f --- /dev/null +++ b/MediaBrowser.Common/Net/NetworkConfigurationFactory.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; + +namespace MediaBrowser.Common.Net; + +/// +/// Defines the . +/// +public class NetworkConfigurationFactory : IConfigurationFactory +{ + /// + /// The GetConfigurations. + /// + /// The . + public IEnumerable GetConfigurations() + { + return new[] + { + new NetworkConfigurationStore() + }; + } +} diff --git a/MediaBrowser.Common/Net/NetworkConfigurationStore.cs b/MediaBrowser.Common/Net/NetworkConfigurationStore.cs new file mode 100644 index 00000000..d2f51870 --- /dev/null +++ b/MediaBrowser.Common/Net/NetworkConfigurationStore.cs @@ -0,0 +1,23 @@ +using MediaBrowser.Common.Configuration; + +namespace MediaBrowser.Common.Net; + +/// +/// A configuration that stores network related settings. +/// +public class NetworkConfigurationStore : ConfigurationStore +{ + /// + /// The name of the configuration in the storage. + /// + public const string StoreKey = "network"; + + /// + /// Initializes a new instance of the class. + /// + public NetworkConfigurationStore() + { + ConfigurationType = typeof(NetworkConfiguration); + Key = StoreKey; + } +} diff --git a/MediaBrowser.Common/Net/NetworkConstants.cs b/MediaBrowser.Common/Net/NetworkConstants.cs new file mode 100644 index 00000000..cec996a1 --- /dev/null +++ b/MediaBrowser.Common/Net/NetworkConstants.cs @@ -0,0 +1,79 @@ +using System.Net; + +namespace MediaBrowser.Common.Net; + +/// +/// Networking constants. +/// +public static class NetworkConstants +{ + /// + /// IPv4 mask bytes. + /// + public const int IPv4MaskBytes = 4; + + /// + /// IPv6 mask bytes. + /// + public const int IPv6MaskBytes = 16; + + /// + /// Minimum IPv4 prefix size. + /// + public const int MinimumIPv4PrefixSize = 32; + + /// + /// Minimum IPv6 prefix size. + /// + public const int MinimumIPv6PrefixSize = 128; + + /// + /// Whole IPv4 address space. + /// + public static readonly IPNetwork IPv4Any = new IPNetwork(IPAddress.Any, 0); + + /// + /// Whole IPv6 address space. + /// + public static readonly IPNetwork IPv6Any = new IPNetwork(IPAddress.IPv6Any, 0); + + /// + /// IPv4 Loopback as defined in RFC 5735. + /// + public static readonly IPNetwork IPv4RFC5735Loopback = new IPNetwork(IPAddress.Loopback, 8); + + /// + /// IPv4 private class A as defined in RFC 1918. + /// + public static readonly IPNetwork IPv4RFC1918PrivateClassA = new IPNetwork(IPAddress.Parse("10.0.0.0"), 8); + + /// + /// IPv4 private class B as defined in RFC 1918. + /// + public static readonly IPNetwork IPv4RFC1918PrivateClassB = new IPNetwork(IPAddress.Parse("172.16.0.0"), 12); + + /// + /// IPv4 private class C as defined in RFC 1918. + /// + public static readonly IPNetwork IPv4RFC1918PrivateClassC = new IPNetwork(IPAddress.Parse("192.168.0.0"), 16); + + /// + /// IPv4 Link-Local as defined in RFC 3927. + /// + public static readonly IPNetwork IPv4RFC3927LinkLocal = new IPNetwork(IPAddress.Parse("169.254.0.0"), 16); + + /// + /// IPv6 loopback as defined in RFC 4291. + /// + public static readonly IPNetwork IPv6RFC4291Loopback = new IPNetwork(IPAddress.IPv6Loopback, 128); + + /// + /// IPv6 site local as defined in RFC 4291. + /// + public static readonly IPNetwork IPv6RFC4291SiteLocal = new IPNetwork(IPAddress.Parse("fe80::"), 10); + + /// + /// IPv6 unique local as defined in RFC 4193. + /// + public static readonly IPNetwork IPv6RFC4193UniqueLocal = new IPNetwork(IPAddress.Parse("fc00::"), 7); +} diff --git a/MediaBrowser.Common/Net/NetworkUtils.cs b/MediaBrowser.Common/Net/NetworkUtils.cs new file mode 100644 index 00000000..5c854b39 --- /dev/null +++ b/MediaBrowser.Common/Net/NetworkUtils.cs @@ -0,0 +1,360 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Net; +using System.Net.Sockets; +using System.Text.RegularExpressions; +using Jellyfin.Extensions; +using MediaBrowser.Model.Net; + +namespace MediaBrowser.Common.Net; + +/// +/// Defines the . +/// +public static partial class NetworkUtils +{ + // Use regular expression as CheckHostName isn't RFC5892 compliant. + // Modified from gSkinner's expression at https://stackoverflow.com/questions/11809631/fully-qualified-domain-name-validation + [GeneratedRegex(@"(?im)^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){0,127}(?![0-9]*$)[a-z0-9-]+\.?)(:(\d){1,5}){0,1}$", RegexOptions.IgnoreCase, "en-US")] + private static partial Regex FqdnGeneratedRegex(); + + /// + /// Returns true if the IPAddress contains an IP6 Local link address. + /// + /// IPAddress object to check. + /// True if it is a local link address. + /// + /// See https://stackoverflow.com/questions/6459928/explain-the-instance-properties-of-system-net-ipaddress + /// it appears that the IPAddress.IsIPv6LinkLocal is out of date. + /// + public static bool IsIPv6LinkLocal(IPAddress address) + { + ArgumentNullException.ThrowIfNull(address); + + if (address.IsIPv4MappedToIPv6) + { + address = address.MapToIPv4(); + } + + if (address.AddressFamily != AddressFamily.InterNetworkV6) + { + return false; + } + + // GetAddressBytes + Span octet = stackalloc byte[16]; + address.TryWriteBytes(octet, out _); + uint word = (uint)(octet[0] << 8) + octet[1]; + + return word >= 0xfe80 && word <= 0xfebf; // fe80::/10 :Local link. + } + + /// + /// Convert a subnet mask in CIDR notation to a dotted decimal string value. IPv4 only. + /// + /// Subnet mask in CIDR notation. + /// IPv4 or IPv6 family. + /// String value of the subnet mask in dotted decimal notation. + public static IPAddress CidrToMask(byte cidr, AddressFamily family) + { + uint addr = 0xFFFFFFFF << ((family == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : NetworkConstants.MinimumIPv6PrefixSize) - cidr); + addr = ((addr & 0xff000000) >> 24) + | ((addr & 0x00ff0000) >> 8) + | ((addr & 0x0000ff00) << 8) + | ((addr & 0x000000ff) << 24); + return new IPAddress(addr); + } + + /// + /// Convert a subnet mask in CIDR notation to a dotted decimal string value. IPv4 only. + /// + /// Subnet mask in CIDR notation. + /// IPv4 or IPv6 family. + /// String value of the subnet mask in dotted decimal notation. + public static IPAddress CidrToMask(int cidr, AddressFamily family) + { + uint addr = 0xFFFFFFFF << ((family == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : NetworkConstants.MinimumIPv6PrefixSize) - cidr); + addr = ((addr & 0xff000000) >> 24) + | ((addr & 0x00ff0000) >> 8) + | ((addr & 0x0000ff00) << 8) + | ((addr & 0x000000ff) << 24); + return new IPAddress(addr); + } + + /// + /// Convert a subnet mask to a CIDR. IPv4 only. + /// https://stackoverflow.com/questions/36954345/get-cidr-from-netmask. + /// + /// Subnet mask. + /// Byte CIDR representing the mask. + public static byte MaskToCidr(IPAddress mask) + { + ArgumentNullException.ThrowIfNull(mask); + + byte cidrnet = 0; + if (mask.Equals(IPAddress.Any)) + { + return cidrnet; + } + + // GetAddressBytes + Span bytes = stackalloc byte[mask.AddressFamily == AddressFamily.InterNetwork ? NetworkConstants.IPv4MaskBytes : NetworkConstants.IPv6MaskBytes]; + if (!mask.TryWriteBytes(bytes, out var bytesWritten)) + { + Console.WriteLine("Unable to write address bytes, only {0} bytes written.", bytesWritten.ToString(CultureInfo.InvariantCulture)); + } + + var zeroed = false; + for (var i = 0; i < bytes.Length; i++) + { + for (int v = bytes[i]; (v & 0xFF) != 0; v <<= 1) + { + if (zeroed) + { + // Invalid netmask. + return (byte)~cidrnet; + } + + if ((v & 0x80) == 0) + { + zeroed = true; + } + else + { + cidrnet++; + } + } + } + + return cidrnet; + } + + /// + /// Converts an IPAddress into a string. + /// IPv6 addresses are returned in [ ], with their scope removed. + /// + /// Address to convert. + /// URI safe conversion of the address. + public static string FormatIPString(IPAddress? address) + { + if (address is null) + { + return string.Empty; + } + + var str = address.ToString(); + if (address.AddressFamily == AddressFamily.InterNetworkV6) + { + int i = str.IndexOf('%', StringComparison.Ordinal); + if (i != -1) + { + str = str.Substring(0, i); + } + + return $"[{str}]"; + } + + return str; + } + + /// + /// Try parsing an array of strings into objects, respecting exclusions. + /// Elements without a subnet mask will be represented as with a single IP. + /// + /// Input string array to be parsed. + /// Collection of . + /// Boolean signaling if negated or not negated values should be parsed. + /// True if parsing was successful. + public static bool TryParseToSubnets(string[] values, [NotNullWhen(true)] out IReadOnlyList? result, bool negated = false) + { + if (values is null || values.Length == 0) + { + result = null; + return false; + } + + List? tmpResult = null; + for (int a = 0; a < values.Length; a++) + { + if (TryParseToSubnet(values[a], out var innerResult, negated)) + { + (tmpResult ??= new()).Add(innerResult); + } + } + + result = tmpResult; + return result is not null; + } + + /// + /// Try parsing a string into an , respecting exclusions. + /// Inputs without a subnet mask will be represented as with a single IP. + /// + /// Input string to be parsed. + /// An . + /// Boolean signaling if negated or not negated values should be parsed. + /// True if parsing was successful. + public static bool TryParseToSubnet(ReadOnlySpan value, [NotNullWhen(true)] out IPData? result, bool negated = false) + { + // If multiple IP addresses are in a comma-separated string, the individual addresses may contain leading and/or trailing whitespace + value = value.Trim(); + + bool isAddressNegated = false; + if (value.StartsWith('!')) + { + isAddressNegated = true; + value = value[1..]; // Remove leading '!' character + } + + if (isAddressNegated != negated) + { + result = default; + return false; + } + + var index = value.IndexOf('/'); + if (index != -1) + { + if (IPAddress.TryParse(value[..index], out var address) && IPNetwork.TryParse(value, out var subnet)) + { + result = new IPData(address, subnet); + return true; + } + } + else if (IPAddress.TryParse(value, out var address)) + { + if (address.AddressFamily == AddressFamily.InterNetwork) + { + result = address.Equals(IPAddress.Any) ? new IPData(IPAddress.Any, NetworkConstants.IPv4Any) : new IPData(address, new IPNetwork(address, NetworkConstants.MinimumIPv4PrefixSize)); + return true; + } + else if (address.AddressFamily == AddressFamily.InterNetworkV6) + { + result = address.Equals(IPAddress.IPv6Any) ? new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any) : new IPData(address, new IPNetwork(address, NetworkConstants.MinimumIPv6PrefixSize)); + return true; + } + } + + result = default; + return false; + } + + /// + /// Attempts to parse a host span. + /// + /// Host name to parse. + /// Object representing the span, if it has successfully been parsed. + /// true if IPv4 is enabled. + /// true if IPv6 is enabled. + /// true if the parsing is successful, false if not. + public static bool TryParseHost(ReadOnlySpan host, [NotNullWhen(true)] out IPAddress[]? addresses, bool isIPv4Enabled = true, bool isIPv6Enabled = false) + { + host = host.Trim(); + if (host.IsEmpty) + { + addresses = null; + return false; + } + + // See if it's an IPv6 with port address e.g. [::1] or [::1]:120. + if (host[0] == '[') + { + int i = host.IndexOf(']'); + if (i != -1) + { + return TryParseHost(host[1..(i - 1)], out addresses); + } + + addresses = Array.Empty(); + return false; + } + + var hosts = new List(); + foreach (var splitSpan in host.Split(':')) + { + hosts.Add(splitSpan.ToString()); + } + + if (hosts.Count <= 2) + { + var firstPart = hosts[0]; + + // Is hostname or hostname:port + if (FqdnGeneratedRegex().IsMatch(firstPart)) + { + try + { + // .NET automatically filters only supported returned addresses based on OS support. + addresses = Dns.GetHostAddresses(firstPart); + return true; + } + catch (SocketException) + { + // Ignore socket errors, as the result value will just be an empty array. + } + } + + // Is an IPv4 or IPv4:port + if (IPAddress.TryParse(firstPart.AsSpan().LeftPart('/'), out var address)) + { + if (((address.AddressFamily == AddressFamily.InterNetwork) && (!isIPv4Enabled && isIPv6Enabled)) + || ((address.AddressFamily == AddressFamily.InterNetworkV6) && (isIPv4Enabled && !isIPv6Enabled))) + { + addresses = Array.Empty(); + return false; + } + + addresses = new[] { address }; + + // Host name is an IPv4 address, so fake resolve. + return true; + } + } + else if (hosts.Count > 0 && hosts.Count <= 9) // 8 octets + port + { + if (IPAddress.TryParse(host.LeftPart('/'), out var address)) + { + addresses = new[] { address }; + return true; + } + } + + addresses = Array.Empty(); + return false; + } + + /// + /// Gets the broadcast address for a . + /// + /// The . + /// The broadcast address. + public static IPAddress GetBroadcastAddress(IPNetwork network) + { + var addressBytes = network.BaseAddress.GetAddressBytes(); + uint ipAddress = BitConverter.ToUInt32(addressBytes, 0); + uint ipMaskV4 = BitConverter.ToUInt32(CidrToMask(network.PrefixLength, AddressFamily.InterNetwork).GetAddressBytes(), 0); + uint broadCastIPAddress = ipAddress | ~ipMaskV4; + + return new IPAddress(BitConverter.GetBytes(broadCastIPAddress)); + } + + /// + /// Check if a subnet contains an address. This method also handles IPv4 mapped to IPv6 addresses. + /// + /// The . + /// The . + /// Whether the supplied IP is in the supplied network. + public static bool SubnetContainsAddress(IPNetwork network, IPAddress address) + { + ArgumentNullException.ThrowIfNull(address); + + if (address.IsIPv4MappedToIPv6) + { + address = address.MapToIPv4(); + } + + return network.Contains(address); + } +} diff --git a/MediaBrowser.Common/Net/RemoteAccessPolicyResult.cs b/MediaBrowser.Common/Net/RemoteAccessPolicyResult.cs new file mode 100644 index 00000000..193d3722 --- /dev/null +++ b/MediaBrowser.Common/Net/RemoteAccessPolicyResult.cs @@ -0,0 +1,29 @@ +using System; + +namespace MediaBrowser.Common.Net; + +/// +/// Result of . +/// +public enum RemoteAccessPolicyResult +{ + /// + /// The connection should be allowed. + /// + Allow, + + /// + /// The connection should be rejected since it is not from a local IP and remote access is disabled. + /// + RejectDueToRemoteAccessDisabled, + + /// + /// The connection should be rejected since it is from a blocklisted IP. + /// + RejectDueToIPBlocklist, + + /// + /// The connection should be rejected since it is from a remote IP that is not in the allowlist. + /// + RejectDueToNotAllowlistedRemoteIP, +} diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs new file mode 100644 index 00000000..8972089a --- /dev/null +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -0,0 +1,94 @@ +#nullable disable + +using System; +using System.IO; +using System.Reflection; +using MediaBrowser.Model.Plugins; + +namespace MediaBrowser.Common.Plugins +{ + /// + /// Provides a common base class for all plugins. + /// + public abstract class BasePlugin : IPlugin, IPluginAssembly + { + /// + /// Gets the name of the plugin. + /// + /// The name. + public abstract string Name { get; } + + /// + /// Gets the description. + /// + /// The description. + public virtual string Description => string.Empty; + + /// + /// Gets the unique id. + /// + /// The unique id. + public virtual Guid Id { get; private set; } + + /// + /// Gets the plugin version. + /// + /// The version. + public Version Version { get; private set; } + + /// + /// Gets the path to the assembly file. + /// + /// The assembly file path. + public string AssemblyFilePath { get; private set; } + + /// + /// Gets the full path to the data folder, where the plugin can store any miscellaneous files needed. + /// + /// The data folder path. + public string DataFolderPath { get; private set; } + + /// + /// Gets a value indicating whether the plugin can be uninstalled. + /// + public bool CanUninstall => !Path.GetDirectoryName(AssemblyFilePath) + .Equals(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), StringComparison.Ordinal); + + /// + /// Gets the plugin info. + /// + /// PluginInfo. + public virtual PluginInfo GetPluginInfo() + { + var info = new PluginInfo( + Name, + Version, + Description, + Id, + CanUninstall); + + return info; + } + + /// + /// Called just before the plugin is uninstalled from the server. + /// + public virtual void OnUninstalling() + { + } + + /// + public void SetAttributes(string assemblyFilePath, string dataFolderPath, Version assemblyVersion) + { + AssemblyFilePath = assemblyFilePath; + DataFolderPath = dataFolderPath; + Version = assemblyVersion; + } + + /// + public void SetId(Guid assemblyId) + { + Id = assemblyId; + } + } +} diff --git a/MediaBrowser.Common/Plugins/BasePluginOfT.cs b/MediaBrowser.Common/Plugins/BasePluginOfT.cs new file mode 100644 index 00000000..30c67fa0 --- /dev/null +++ b/MediaBrowser.Common/Plugins/BasePluginOfT.cs @@ -0,0 +1,200 @@ +#nullable disable +#pragma warning disable SA1649 // File name should match first type name + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Serialization; + +namespace MediaBrowser.Common.Plugins +{ + /// + /// Provides a common base class for all plugins. + /// + /// The type of the T configuration type. + public abstract class BasePlugin : BasePlugin, IHasPluginConfiguration + where TConfigurationType : BasePluginConfiguration + { + /// + /// The configuration sync lock. + /// + private readonly Lock _configurationSyncLock = new(); + + /// + /// The configuration save lock. + /// + private readonly Lock _configurationSaveLock = new(); + + /// + /// The configuration. + /// + private TConfigurationType _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The application paths. + /// The XML serializer. + protected BasePlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + { + ApplicationPaths = applicationPaths; + XmlSerializer = xmlSerializer; + + var assembly = GetType().Assembly; + var assemblyName = assembly.GetName(); + var assemblyFilePath = assembly.Location; + + var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); + if (Version is not null && !Directory.Exists(dataFolderPath)) + { + // Try again with the version number appended to the folder name. + dataFolderPath += "_" + Version; + } + + SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); + + var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true); + if (idAttributes.Length > 0) + { + var attribute = (GuidAttribute)idAttributes[0]; + var assemblyId = new Guid(attribute.Value); + + SetId(assemblyId); + } + } + + /// + /// Gets the application paths. + /// + /// The application paths. + protected IApplicationPaths ApplicationPaths { get; private set; } + + /// + /// Gets the XML serializer. + /// + /// The XML serializer. + protected IXmlSerializer XmlSerializer { get; private set; } + + /// + /// Gets the type of configuration this plugin uses. + /// + /// The type of the configuration. + public Type ConfigurationType => typeof(TConfigurationType); + + /// + /// Gets or sets the event handler that is triggered when this configuration changes. + /// + public EventHandler ConfigurationChanged { get; set; } + + /// + /// Gets the name the assembly file. + /// + /// The name of the assembly file. + protected string AssemblyFileName => Path.GetFileName(AssemblyFilePath); + + /// + /// Gets or sets the plugin configuration. + /// + /// The configuration. + public TConfigurationType Configuration + { + get + { + // Lazy load + if (_configuration is null) + { + lock (_configurationSyncLock) + { + _configuration ??= LoadConfiguration(); + } + } + + return _configuration; + } + + protected set => _configuration = value; + } + + /// + /// Gets the name of the configuration file. Subclasses should override. + /// + /// The name of the configuration file. + public virtual string ConfigurationFileName => Path.ChangeExtension(AssemblyFileName, ".xml"); + + /// + /// Gets the full path to the configuration file. + /// + /// The configuration file path. + public string ConfigurationFilePath => Path.Combine(ApplicationPaths.PluginConfigurationsPath, ConfigurationFileName); + + /// + /// Gets the plugin configuration. + /// + /// The configuration. + BasePluginConfiguration IHasPluginConfiguration.Configuration => Configuration; + + /// + /// Saves the current configuration to the file system. + /// + /// Configuration to save. + public virtual void SaveConfiguration(TConfigurationType config) + { + lock (_configurationSaveLock) + { + var folder = Path.GetDirectoryName(ConfigurationFilePath); + Directory.CreateDirectory(folder); + + XmlSerializer.SerializeToFile(config, ConfigurationFilePath); + } + } + + /// + /// Saves the current configuration to the file system. + /// + public virtual void SaveConfiguration() + { + SaveConfiguration(Configuration); + } + + /// + public virtual void UpdateConfiguration(BasePluginConfiguration configuration) + { + ArgumentNullException.ThrowIfNull(configuration); + + Configuration = (TConfigurationType)configuration; + + SaveConfiguration(Configuration); + + ConfigurationChanged?.Invoke(this, configuration); + } + + /// + public override PluginInfo GetPluginInfo() + { + var info = base.GetPluginInfo(); + + info.ConfigurationFileName = ConfigurationFileName; + + return info; + } + + private TConfigurationType LoadConfiguration() + { + var path = ConfigurationFilePath; + + try + { + return (TConfigurationType)XmlSerializer.DeserializeFromFile(typeof(TConfigurationType), path); + } + catch + { + var config = Activator.CreateInstance(); + SaveConfiguration(config); + return config; + } + } + } +} diff --git a/MediaBrowser.Common/Plugins/IHasPluginConfiguration.cs b/MediaBrowser.Common/Plugins/IHasPluginConfiguration.cs new file mode 100644 index 00000000..af9272ca --- /dev/null +++ b/MediaBrowser.Common/Plugins/IHasPluginConfiguration.cs @@ -0,0 +1,27 @@ +using System; +using MediaBrowser.Model.Plugins; + +namespace MediaBrowser.Common.Plugins +{ + /// + /// Defines the . + /// + public interface IHasPluginConfiguration + { + /// + /// Gets the type of configuration this plugin uses. + /// + Type ConfigurationType { get; } + + /// + /// Gets the plugin's configuration. + /// + BasePluginConfiguration Configuration { get; } + + /// + /// Completely overwrites the current configuration with a new copy. + /// + /// The configuration. + void UpdateConfiguration(BasePluginConfiguration configuration); + } +} diff --git a/MediaBrowser.Common/Plugins/IPlugin.cs b/MediaBrowser.Common/Plugins/IPlugin.cs new file mode 100644 index 00000000..01e0a536 --- /dev/null +++ b/MediaBrowser.Common/Plugins/IPlugin.cs @@ -0,0 +1,59 @@ +#nullable disable + +using System; +using MediaBrowser.Model.Plugins; + +namespace MediaBrowser.Common.Plugins +{ + /// + /// Defines the . + /// + public interface IPlugin + { + /// + /// Gets the name of the plugin. + /// + string Name { get; } + + /// + /// Gets the Description. + /// + string Description { get; } + + /// + /// Gets the unique id. + /// + Guid Id { get; } + + /// + /// Gets the plugin version. + /// + Version Version { get; } + + /// + /// Gets the path to the assembly file. + /// + string AssemblyFilePath { get; } + + /// + /// Gets a value indicating whether the plugin can be uninstalled. + /// + bool CanUninstall { get; } + + /// + /// Gets the full path to the data folder, where the plugin can store any miscellaneous files needed. + /// + string DataFolderPath { get; } + + /// + /// Gets the . + /// + /// PluginInfo. + PluginInfo GetPluginInfo(); + + /// + /// Called when just before the plugin is uninstalled from the server. + /// + void OnUninstalling(); + } +} diff --git a/MediaBrowser.Common/Plugins/IPluginAssembly.cs b/MediaBrowser.Common/Plugins/IPluginAssembly.cs new file mode 100644 index 00000000..6df4fbb7 --- /dev/null +++ b/MediaBrowser.Common/Plugins/IPluginAssembly.cs @@ -0,0 +1,13 @@ +#pragma warning disable CS1591 + +using System; + +namespace MediaBrowser.Common.Plugins +{ + public interface IPluginAssembly + { + void SetAttributes(string assemblyFilePath, string dataFolderPath, Version assemblyVersion); + + void SetId(Guid assemblyId); + } +} diff --git a/MediaBrowser.Common/Plugins/IPluginManager.cs b/MediaBrowser.Common/Plugins/IPluginManager.cs new file mode 100644 index 00000000..0ff9719e --- /dev/null +++ b/MediaBrowser.Common/Plugins/IPluginManager.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading.Tasks; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Updates; +using Microsoft.Extensions.DependencyInjection; + +namespace MediaBrowser.Common.Plugins +{ + /// + /// Defines the . + /// + public interface IPluginManager + { + /// + /// Gets the Plugins. + /// + IReadOnlyList Plugins { get; } + + /// + /// Creates the plugins. + /// + void CreatePlugins(); + + /// + /// Returns all the assemblies. + /// + /// An IEnumerable{Assembly}. + IEnumerable LoadAssemblies(); + + /// + /// Registers the plugin's services with the DI. + /// Note: DI is not yet instantiated yet. + /// + /// A instance. + void RegisterServices(IServiceCollection serviceCollection); + + /// + /// Saves the manifest back to disk. + /// + /// The to save. + /// The path where to save the manifest. + /// True if successful. + bool SaveManifest(PluginManifest manifest, string path); + + /// + /// Generates a manifest from repository data. + /// + /// The used to generate a manifest. + /// Version to be installed. + /// The path where to save the manifest. + /// Initial status of the plugin. + /// True if successful. + Task PopulateManifest(PackageInfo packageInfo, Version version, string path, PluginStatus status); + + /// + /// Imports plugin details from a folder. + /// + /// Folder of the plugin. + void ImportPluginFrom(string folder); + + /// + /// Disable the plugin. + /// + /// The of the plug to disable. + void FailPlugin(Assembly assembly); + + /// + /// Disable the plugin. + /// + /// The of the plug to disable. + void DisablePlugin(LocalPlugin plugin); + + /// + /// Enables the plugin, disabling all other versions. + /// + /// The of the plug to disable. + void EnablePlugin(LocalPlugin plugin); + + /// + /// Attempts to find the plugin with and id of . + /// + /// Id of plugin. + /// The version of the plugin to locate. + /// A if located, or null if not. + LocalPlugin? GetPlugin(Guid id, Version? version = null); + + /// + /// Removes the plugin. + /// + /// The plugin. + /// Outcome of the operation. + bool RemovePlugin(LocalPlugin plugin); + } +} diff --git a/MediaBrowser.Common/Plugins/LocalPlugin.cs b/MediaBrowser.Common/Plugins/LocalPlugin.cs new file mode 100644 index 00000000..96af423c --- /dev/null +++ b/MediaBrowser.Common/Plugins/LocalPlugin.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Model.Plugins; + +namespace MediaBrowser.Common.Plugins +{ + /// + /// Local plugin class. + /// + public class LocalPlugin : IEquatable + { + private readonly bool _supported; + private Version? _version; + + /// + /// Initializes a new instance of the class. + /// + /// The plugin path. + /// True if Jellyfin supports this version of the plugin. + /// The manifest record for this plugin, or null if one does not exist. + public LocalPlugin(string path, bool isSupported, PluginManifest manifest) + { + Path = path; + DllFiles = Array.Empty(); + _supported = isSupported; + Manifest = manifest; + } + + /// + /// Gets the plugin id. + /// + public Guid Id => Manifest.Id; + + /// + /// Gets the plugin name. + /// + public string Name => Manifest.Name; + + /// + /// Gets the plugin version. + /// + public Version Version + { + get + { + if (_version is null) + { + _version = Version.Parse(Manifest.Version); + } + + return _version; + } + } + + /// + /// Gets the plugin path. + /// + public string Path { get; } + + /// + /// Gets or sets the list of dll files for this plugin. + /// + public IReadOnlyList DllFiles { get; set; } + + /// + /// Gets or sets the instance of this plugin. + /// + public IPlugin? Instance { get; set; } + + /// + /// Gets a value indicating whether Jellyfin supports this version of the plugin, and it's enabled. + /// + public bool IsEnabledAndSupported => _supported && Manifest.Status >= PluginStatus.Active; + + /// + /// Gets a value indicating whether the plugin has a manifest. + /// + public PluginManifest Manifest { get; } + + /// + /// Compare two . + /// + /// The first item. + /// The second item. + /// Comparison result. + public static int Compare(LocalPlugin a, LocalPlugin b) + { + if (a is null || b is null) + { + throw new ArgumentNullException(a is null ? nameof(a) : nameof(b)); + } + + var compare = string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); + + // Id is not equal but name is. + if (!a.Id.Equals(b.Id) && compare == 0) + { + compare = a.Id.CompareTo(b.Id); + } + + return compare == 0 ? a.Version.CompareTo(b.Version) : compare; + } + + /// + /// Returns the plugin information. + /// + /// A instance containing the information. + public PluginInfo GetPluginInfo() + { + var inst = Instance?.GetPluginInfo() ?? new PluginInfo(Manifest.Name, Version, Manifest.Description, Manifest.Id, true); + inst.Status = Manifest.Status; + inst.HasImage = !string.IsNullOrEmpty(Manifest.ImagePath); + return inst; + } + + /// + public override bool Equals(object? obj) + { + return obj is LocalPlugin other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return Name.GetHashCode(StringComparison.OrdinalIgnoreCase); + } + + /// + public bool Equals(LocalPlugin? other) + { + if (other is null) + { + return false; + } + + return Name.Equals(other.Name, StringComparison.OrdinalIgnoreCase) && Id.Equals(other.Id) && Version.Equals(other.Version); + } + } +} diff --git a/MediaBrowser.Common/Plugins/PluginManifest.cs b/MediaBrowser.Common/Plugins/PluginManifest.cs new file mode 100644 index 00000000..e0847cce --- /dev/null +++ b/MediaBrowser.Common/Plugins/PluginManifest.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using MediaBrowser.Model.Plugins; + +namespace MediaBrowser.Common.Plugins +{ + /// + /// Defines a Plugin manifest file. + /// + public class PluginManifest + { + /// + /// Initializes a new instance of the class. + /// + public PluginManifest() + { + Category = string.Empty; + Changelog = string.Empty; + Description = string.Empty; + Id = Guid.Empty; + Name = string.Empty; + Owner = string.Empty; + Overview = string.Empty; + TargetAbi = string.Empty; + Version = string.Empty; + Assemblies = Array.Empty(); + } + + /// + /// Gets or sets the category of the plugin. + /// + [JsonPropertyName("category")] + public string Category { get; set; } + + /// + /// Gets or sets the changelog information. + /// + [JsonPropertyName("changelog")] + public string Changelog { get; set; } + + /// + /// Gets or sets the description of the plugin. + /// + [JsonPropertyName("description")] + public string Description { get; set; } + + /// + /// Gets or sets the Global Unique Identifier for the plugin. + /// + [JsonPropertyName("guid")] + public Guid Id { get; set; } + + /// + /// Gets or sets the Name of the plugin. + /// + [JsonPropertyName("name")] + public string Name { get; set; } + + /// + /// Gets or sets an overview of the plugin. + /// + [JsonPropertyName("overview")] + public string Overview { get; set; } + + /// + /// Gets or sets the owner of the plugin. + /// + [JsonPropertyName("owner")] + public string Owner { get; set; } + + /// + /// Gets or sets the compatibility version for the plugin. + /// + [JsonPropertyName("targetAbi")] + public string TargetAbi { get; set; } + + /// + /// Gets or sets the timestamp of the plugin. + /// + [JsonPropertyName("timestamp")] + public DateTime Timestamp { get; set; } + + /// + /// Gets or sets the Version number of the plugin. + /// + [JsonPropertyName("version")] + public string Version { get; set; } + + /// + /// Gets or sets a value indicating the operational status of this plugin. + /// + [JsonPropertyName("status")] + public PluginStatus Status { get; set; } + + /// + /// Gets or sets a value indicating whether this plugin should automatically update. + /// + [JsonPropertyName("autoUpdate")] + public bool AutoUpdate { get; set; } = true; // DO NOT MOVE THIS INTO THE CONSTRUCTOR. + + /// + /// Gets or sets the ImagePath + /// Gets or sets a value indicating whether this plugin has an image. + /// Image must be located in the local plugin folder. + /// + [JsonPropertyName("imagePath")] + public string? ImagePath { get; set; } + + /// + /// Gets or sets the collection of assemblies that should be loaded. + /// Paths are considered relative to the plugin folder. + /// + [JsonPropertyName("assemblies")] + public IReadOnlyList Assemblies { get; set; } + } +} diff --git a/MediaBrowser.Common/Properties/AssemblyInfo.cs b/MediaBrowser.Common/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..538e89fd --- /dev/null +++ b/MediaBrowser.Common/Properties/AssemblyInfo.cs @@ -0,0 +1,21 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MediaBrowser.Common")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Jellyfin Project")] +[assembly: AssemblyProduct("Jellyfin Server")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] diff --git a/MediaBrowser.Common/Providers/ProviderIdParsers.cs b/MediaBrowser.Common/Providers/ProviderIdParsers.cs new file mode 100644 index 00000000..fb3d6cd7 --- /dev/null +++ b/MediaBrowser.Common/Providers/ProviderIdParsers.cs @@ -0,0 +1,123 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace MediaBrowser.Common.Providers +{ + /// + /// Parsers for provider ids. + /// + public static class ProviderIdParsers + { + private const int ImdbMinNumbers = 7; + private const int ImdbMaxNumbers = 8; + private const string ImdbPrefix = "tt"; + + /// + /// Parses an IMDb id from a string. + /// + /// The text to parse. + /// The parsed IMDb id. + /// True if parsing was successful, false otherwise. + public static bool TryFindImdbId(ReadOnlySpan text, out ReadOnlySpan imdbId) + { + // IMDb id is at least 9 chars (tt + 7 numbers) + while (text.Length >= 2 + ImdbMinNumbers) + { + var ttPos = text.IndexOf(ImdbPrefix); + if (ttPos == -1) + { + imdbId = default; + return false; + } + + text = text.Slice(ttPos); + var i = 2; + var limit = Math.Min(text.Length, ImdbMaxNumbers + 2); + for (; i < limit; i++) + { + var c = text[i]; + if (!IsDigit(c)) + { + break; + } + } + + // Skip if more than 8 digits + 2 chars for tt + if (i <= ImdbMaxNumbers + 2 && i >= ImdbMinNumbers + 2) + { + imdbId = text.Slice(0, i); + return true; + } + + text = text.Slice(i); + } + + imdbId = default; + return false; + } + + /// + /// Parses an TMDb id from a movie url. + /// + /// The text with the url to parse. + /// The parsed TMDb id. + /// True if parsing was successful, false otherwise. + public static bool TryFindTmdbMovieId(ReadOnlySpan text, out ReadOnlySpan tmdbId) + => TryFindProviderId(text, "themoviedb.org/movie/", out tmdbId); + + /// + /// Parses an TMDb id from a series url. + /// + /// The text with the url to parse. + /// The parsed TMDb id. + /// True if parsing was successful, false otherwise. + public static bool TryFindTmdbSeriesId(ReadOnlySpan text, out ReadOnlySpan tmdbId) + => TryFindProviderId(text, "themoviedb.org/tv/", out tmdbId); + + /// + /// Parses an TVDb id from a url. + /// + /// The text with the url to parse. + /// The parsed TVDb id. + /// True if parsing was successful, false otherwise. + public static bool TryFindTvdbId(ReadOnlySpan text, out ReadOnlySpan tvdbId) + => TryFindProviderId(text, "thetvdb.com/?tab=series&id=", out tvdbId); + + private static bool TryFindProviderId(ReadOnlySpan text, ReadOnlySpan searchString, [NotNullWhen(true)] out ReadOnlySpan providerId) + { + var searchPos = text.IndexOf(searchString); + if (searchPos == -1) + { + providerId = default; + return false; + } + + text = text.Slice(searchPos + searchString.Length); + + int i = 0; + for (; i < text.Length; i++) + { + var c = text[i]; + + if (!IsDigit(c)) + { + break; + } + } + + if (i >= 1) + { + providerId = text.Slice(0, i); + return true; + } + + providerId = default; + return false; + } + + private static bool IsDigit(char c) + { + return c >= '0' && c <= '9'; + } + } +} diff --git a/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs b/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs new file mode 100644 index 00000000..0445397a --- /dev/null +++ b/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs @@ -0,0 +1,21 @@ +#pragma warning disable CS1591 + +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Common.Providers +{ + public class SubtitleConfigurationFactory : IConfigurationFactory + { + /// + public IEnumerable GetConfigurations() + { + yield return new ConfigurationStore() + { + Key = "subtitles", + ConfigurationType = typeof(SubtitleOptions) + }; + } + } +} diff --git a/MediaBrowser.Common/RequiresSourceSerialisationAttribute.cs b/MediaBrowser.Common/RequiresSourceSerialisationAttribute.cs new file mode 100644 index 00000000..b22e7cba --- /dev/null +++ b/MediaBrowser.Common/RequiresSourceSerialisationAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace MediaBrowser.Common; + +/// +/// Marks a BaseItem as needing custom serialisation from the Data field of the db. +/// +[System.AttributeUsage(System.AttributeTargets.Class, Inherited = true, AllowMultiple = false)] +public sealed class RequiresSourceSerialisationAttribute : System.Attribute +{ +} diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs new file mode 100644 index 00000000..458494bd --- /dev/null +++ b/MediaBrowser.Common/Updates/IInstallationManager.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Updates; + +namespace MediaBrowser.Common.Updates +{ + /// + /// Defines the . + /// + public interface IInstallationManager : IDisposable + { + /// + /// Gets the completed installations. + /// + IEnumerable CompletedInstallations { get; } + + /// + /// Parses a plugin manifest at the supplied URL. + /// + /// Name of the repository. + /// The URL to query. + /// Filter out incompatible plugins. + /// The cancellation token. + /// Task{IReadOnlyList{PackageInfo}}. + Task GetPackages(string manifestName, string manifest, bool filterIncompatible, CancellationToken cancellationToken = default); + + /// + /// Gets all available packages that are supported by this version. + /// + /// The cancellation token. + /// Task{IReadOnlyList{PackageInfo}}. + Task> GetAvailablePackages(CancellationToken cancellationToken = default); + + /// + /// Returns all plugins matching the requirements. + /// + /// The available packages. + /// The name of the plugin. + /// The id of the plugin. + /// The version of the plugin. + /// All plugins matching the requirements. + IEnumerable FilterPackages( + IEnumerable availablePackages, + string? name = null, + Guid id = default, + Version? specificVersion = null); + + /// + /// Returns all compatible versions ordered from newest to oldest. + /// + /// The available packages. + /// The name. + /// The id of the plugin. + /// The minimum required version of the plugin. + /// The specific version of the plugin to install. + /// All compatible versions ordered from newest to oldest. + IEnumerable GetCompatibleVersions( + IEnumerable availablePackages, + string? name = null, + Guid id = default, + Version? minVersion = null, + Version? specificVersion = null); + + /// + /// Returns the available compatible plugin updates. + /// + /// The cancellation token. + /// The available plugin updates. + Task> GetAvailablePluginUpdates(CancellationToken cancellationToken = default); + + /// + /// Installs the package. + /// + /// The package. + /// The cancellation token. + /// . + Task InstallPackage(InstallationInfo package, CancellationToken cancellationToken = default); + + /// + /// Uninstalls a plugin. + /// + /// The plugin. + void UninstallPlugin(LocalPlugin plugin); + + /// + /// Cancels the installation. + /// + /// The id of the package that is being installed. + /// Returns true if the install was cancelled. + bool CancelInstallation(Guid id); + } +} diff --git a/MediaBrowser.Common/Updates/InstallationEventArgs.cs b/MediaBrowser.Common/Updates/InstallationEventArgs.cs new file mode 100644 index 00000000..f4f75995 --- /dev/null +++ b/MediaBrowser.Common/Updates/InstallationEventArgs.cs @@ -0,0 +1,23 @@ +#nullable disable + +using System; +using MediaBrowser.Model.Updates; + +namespace MediaBrowser.Common.Updates +{ + /// + /// Defines the . + /// + public class InstallationEventArgs : EventArgs + { + /// + /// Gets or sets the . + /// + public InstallationInfo InstallationInfo { get; set; } + + /// + /// Gets or sets the . + /// + public VersionInfo VersionInfo { get; set; } + } +} diff --git a/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs b/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs new file mode 100644 index 00000000..d3714619 --- /dev/null +++ b/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs @@ -0,0 +1,12 @@ +#nullable disable +#pragma warning disable CS1591 + +using System; + +namespace MediaBrowser.Common.Updates +{ + public class InstallationFailedEventArgs : InstallationEventArgs + { + public Exception Exception { get; set; } + } +} diff --git a/MediaBrowser.Common/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs b/MediaBrowser.Common/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs new file mode 100644 index 00000000..925b1351 --- /dev/null +++ b/MediaBrowser.Common/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")] diff --git a/MediaBrowser.Common/obj/Debug/net10.0/MediaBrowser.Common.GeneratedMSBuildEditorConfig.editorconfig b/MediaBrowser.Common/obj/Debug/net10.0/MediaBrowser.Common.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 00000000..f76c3943 --- /dev/null +++ b/MediaBrowser.Common/obj/Debug/net10.0/MediaBrowser.Common.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,27 @@ +is_global = true +build_property.TargetFramework = net10.0 +build_property.TargetFramework = net10.0 +build_property.TargetFrameworkIdentifier = .NETCoreApp +build_property.TargetFrameworkIdentifier = .NETCoreApp +build_property.TargetFrameworkVersion = v10.0 +build_property.TargetFrameworkVersion = v10.0 +build_property.TargetPlatformMinVersion = +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = MediaBrowser.Common +build_property.ProjectDir = /srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 10.0 +build_property.EnableCodeStyleSeverity = diff --git a/MediaBrowser.Common/obj/Debug/net10.0/MediaBrowser.Common.assets.cache b/MediaBrowser.Common/obj/Debug/net10.0/MediaBrowser.Common.assets.cache new file mode 100644 index 0000000000000000000000000000000000000000..96ea2f341a32eb73059b2ec0bb1c8c8af56578e1 GIT binary patch literal 7601 zcmd5>OK%)S5O#>yjty~wZ9<-42l8^_o?XA<1d_0^<7o4+tk^+9Af(=zjy=ij%xHQx z&T>b9a^QkcZYW1ih@ZlRGbgV604`L`vv%BWuVc%2q|(gPR9Aids;;V8x_`I&PFGjg zt=H#YuK)4-7q5P){r1nwudC1h{&(QW{jdHS`{~u|0}sCb^ZWO^x(`p+I;QsUf2XbzCjR*cVu)gdRt8ha*Y&{8hL#%tjGt&zM%ZIa4hUuN$ zVmR+aDi%@|bErZ&m_5m0_QL&3?SxAa*p9cF0x`<0GQG*PF2|y&LAt_lN#hFZ1Nu%s zAhN-OFf}Jxqv^n~tObHwwwuGKunL>3D2HPi6{H{}d)THR7CsmUGZD1t1x&{RR7lkQ5902pp)xK#je_LYx6b03f0% zyQ@WF+1w25&<0Nfo1RS>j$Jpv^<0B>GM|0JUZR7+YXAs)c?9Sv(7QlOS{t_6oN|pB zSbeNK&K-Y^vq>20#1c$_Yw^H>88B*dkEa%?u|ZId#)g2<*f5a0wJ{b3+?9?Uihze+ zunpTQZ4D`u_rNrTatz+DX`wuwa1C5n<+Q6a@OW>ct%Go6L|Tu-9Xoge$kS@gy|}{Z z7>TP%#und<++(X;44K)J2^MrLh~*T}5Bd$S1;cnaxjaafI?&jDeid>#l7vI{^Lfi3}!0bK^V0#wdNVT%dF;}T-z zR5?bts2tx_%Ey6!tS~r9JeI1!I15!^oTXC1fPbZ+e8d_o9;*jVv9a(aG*cCBz7gO7=1uArNRZ z@`h0e(MF??J&isH1{!_rm#Bllqfy6xh&Bi`8g1<0d?p%4V0Z57`Al>d&u4UZ`WT?| z9Hj?z;YGaw9_^T;Oae^-O#{sUT?Lv2+FdUJVN?o&dI_66z~56ZL@-h>gcvagQepfQ`1{365%|gpN6r3i?gp@4(9uizA>Z^>S$OERf3LE#T`tROdNX>Kv#SMBA8Ssce4+ ze7rDdJ;#`FPe4NW#CF*{4|)3=Go>GRPq9aMRe|vNHjoJ|Wni|;*SQoMIjq+39npZ= zLX;EBttAR}=MuZ~aGx&Pz4Iazu8isdSx<6aLDQW?6ZQbF1)zb&=3Nmk#l`fRHLMEz zhp5cwP+kXnrKrN*c|O(?hxeUkP(5+orI`No#1+@hGN7I~YS(2ueOaNBB&*OhDBK-h z;^A7&O-AO#re(Z1tvy<-#+}wxTrDhug30loGZ_EZX-{y(L$VHm!f*mFd4Eq+XexWcc*i`v7pnv2>&B`lN~Tr zleo2Q(+-{1yZ|2}tkME^q&W0QNY8mLtnQVdDY|hXUDYOUoBzUksBPEC{vDYe{eYU( zYE%E+sOj5m;|GPJ8Zi*3gsY#x`nE}vW2D*tk1sREY#-?U49~PG+=A|SSr5FKy z>ggP6;)n2Lo-B3WChDG+Q4jtSq!65{KvQeB&-7Q@f0AFjS0dJ3U)LEg$*MR zUesVrn7z5i>EtR^=Y0xG60c~6d}T~(p0rf@YEj6BIS81Cpaq8+km+TFl$rQBvzI_J6Ejep z5tJp60k~8F3ZNDhL}jJ9lGi7>_N>fXd9ur+(>jBhN$72_$g~-^Ql4JPJ!5(8fQiwG z9$j6Q2NU=tuQFP!UVWhW-JhIGa`QiFs4yfrT>SIuTqO?gC1z)A>vP=26+@2ff1dPr!@oC&RFC^`8r=4KcxeGRD}A2g zer@{7%=L_JK;Iz(9vmjblxJzimL?|VDQ1@DiI!%TiD|H~Va&%H?zx%CMft_~X(f88 zc_o=8m1#wZxvAy(McK*uMXCCRpiFEGixLn|-xsdI6|BJxrokDg0g;goDJa;)fXjDT zygC@Upblk*(iFtwXDdw>|CQGrPHtATn*6k;WlPZsKkhC*sV9BuD}Q{uH#ffCHfW9V zldhu@j34hz<=QtvcCoM4JnL1rMWn=!*L^;kdN=MVtM|n7$-8Z`cGwrKwp#zvmjCb} zlkPJA^&2)GD7Jkn61i@*TLg2<{<$Sb9yO+HKl{l2$HZOioicOVwJz-j$K%?al@^+J z*Cj9};`KKoh9O~0OcW=kBw3nU8e14yrlcgMni;^OxRa6mIMz!{Dh3v#$tA$rvzYV< zcSO>Sh<$l`K4|P~;SB{nWbMN@DdZ6!2vkiMt(2sTqSTy3P@+i8Aw5+D!Sy22gw`re zXqwQ#9~=nH!#73b5E}$kO%Jwsi;05#oSaI13q3PEQ&@=)s)PeT!iZ>$eKoOH3tX_X zf@Q#k9;_q+(G(Qmq8yTsJ^iPvSy}D9*cZp+zQHet@1D!Dqp|^8U9T{odtI~J=wk2g zJ$Ge0R@7D(D`X30KQ!5pFvo0_&1Tt4PwFgw)WClt=*D3ylQSDi#qF TT#z^dNe|!X@I`o%ig5w}H5SN* literal 0 HcmV?d00001 diff --git a/MediaBrowser.Common/obj/MediaBrowser.Common.csproj.nuget.dgspec.json b/MediaBrowser.Common/obj/MediaBrowser.Common.csproj.nuget.dgspec.json new file mode 100644 index 00000000..d48d00c6 --- /dev/null +++ b/MediaBrowser.Common/obj/MediaBrowser.Common.csproj.nuget.dgspec.json @@ -0,0 +1,2862 @@ +{ + "format": 1, + "restore": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": {} + }, + "projects": { + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "projectName": "Jellyfin.Data", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.Extensions.Logging": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj", + "projectName": "Jellyfin.Common", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.AspNetCore": "(,10.0.32767]", + "Microsoft.AspNetCore.Antiforgery": "(,10.0.32767]", + "Microsoft.AspNetCore.App": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.BearerToken": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Cookies": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.OAuth": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization.Policy": "(,10.0.32767]", + "Microsoft.AspNetCore.Components": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Endpoints": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Forms": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Server": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Web": "(,10.0.32767]", + "Microsoft.AspNetCore.Connections.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.CookiePolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.Internal": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.AspNetCore.HostFiltering": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Html.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Features": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Results": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpLogging": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpOverrides": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpsPolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Identity": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Metadata": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ApiExplorer": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.DataAnnotations": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Xml": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.RazorPages": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.TagHelpers": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ViewFeatures": "(,10.0.32767]", + "Microsoft.AspNetCore.OutputCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.RateLimiting": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor.Runtime": "(,10.0.32767]", + "Microsoft.AspNetCore.RequestDecompression": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCompression": "(,10.0.32767]", + "Microsoft.AspNetCore.Rewrite": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.HttpSys": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IIS": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IISIntegration": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets": "(,10.0.32767]", + "Microsoft.AspNetCore.Session": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticAssets": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticFiles": "(,10.0.32767]", + "Microsoft.AspNetCore.WebSockets": "(,10.0.32767]", + "Microsoft.AspNetCore.WebUtilities": "(,10.0.32767]", + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.Extensions.Caching.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Caching.Memory": "(,10.0.32767]", + "Microsoft.Extensions.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Binder": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.CommandLine": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.FileExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Ini": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Json": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.KeyPerFile": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.UserSecrets": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Xml": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Features": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Composite": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Physical": "(,10.0.32767]", + "Microsoft.Extensions.FileSystemGlobbing": "(,10.0.32767]", + "Microsoft.Extensions.Hosting": "(,10.0.32767]", + "Microsoft.Extensions.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Http": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Core": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Stores": "(,10.0.32767]", + "Microsoft.Extensions.Localization": "(,10.0.32767]", + "Microsoft.Extensions.Localization.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Console": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Debug": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventLog": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventSource": "(,10.0.32767]", + "Microsoft.Extensions.Logging.TraceSource": "(,10.0.32767]", + "Microsoft.Extensions.ObjectPool": "(,10.0.32767]", + "Microsoft.Extensions.Options": "(,10.0.32767]", + "Microsoft.Extensions.Options.ConfigurationExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Options.DataAnnotations": "(,10.0.32767]", + "Microsoft.Extensions.Primitives": "(,10.0.32767]", + "Microsoft.Extensions.Validation": "(,10.0.32767]", + "Microsoft.Extensions.WebEncoders": "(,10.0.32767]", + "Microsoft.JSInterop": "(,10.0.32767]", + "Microsoft.Net.Http.Headers": "(,10.0.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.EventLog": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Cbor": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Cryptography.Xml": "(,10.0.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.RateLimiting": "(,10.0.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj", + "projectName": "Jellyfin.Model", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/Jellyfin.Data/Jellyfin.Data.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "MimeTypes": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[2.5.2, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.AspNetCore": "(,10.0.32767]", + "Microsoft.AspNetCore.Antiforgery": "(,10.0.32767]", + "Microsoft.AspNetCore.App": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.BearerToken": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Cookies": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.OAuth": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization.Policy": "(,10.0.32767]", + "Microsoft.AspNetCore.Components": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Endpoints": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Forms": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Server": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Web": "(,10.0.32767]", + "Microsoft.AspNetCore.Connections.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.CookiePolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.Internal": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.AspNetCore.HostFiltering": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Html.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Features": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Results": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpLogging": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpOverrides": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpsPolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Identity": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Metadata": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ApiExplorer": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.DataAnnotations": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Xml": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.RazorPages": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.TagHelpers": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ViewFeatures": "(,10.0.32767]", + "Microsoft.AspNetCore.OutputCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.RateLimiting": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor.Runtime": "(,10.0.32767]", + "Microsoft.AspNetCore.RequestDecompression": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCompression": "(,10.0.32767]", + "Microsoft.AspNetCore.Rewrite": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.HttpSys": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IIS": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IISIntegration": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets": "(,10.0.32767]", + "Microsoft.AspNetCore.Session": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticAssets": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticFiles": "(,10.0.32767]", + "Microsoft.AspNetCore.WebSockets": "(,10.0.32767]", + "Microsoft.AspNetCore.WebUtilities": "(,10.0.32767]", + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.Extensions.Caching.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Caching.Memory": "(,10.0.32767]", + "Microsoft.Extensions.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Binder": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.CommandLine": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.FileExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Ini": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Json": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.KeyPerFile": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.UserSecrets": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Xml": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Features": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Composite": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Physical": "(,10.0.32767]", + "Microsoft.Extensions.FileSystemGlobbing": "(,10.0.32767]", + "Microsoft.Extensions.Hosting": "(,10.0.32767]", + "Microsoft.Extensions.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Http": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Core": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Stores": "(,10.0.32767]", + "Microsoft.Extensions.Localization": "(,10.0.32767]", + "Microsoft.Extensions.Localization.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Console": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Debug": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventLog": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventSource": "(,10.0.32767]", + "Microsoft.Extensions.Logging.TraceSource": "(,10.0.32767]", + "Microsoft.Extensions.ObjectPool": "(,10.0.32767]", + "Microsoft.Extensions.Options": "(,10.0.32767]", + "Microsoft.Extensions.Options.ConfigurationExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Options.DataAnnotations": "(,10.0.32767]", + "Microsoft.Extensions.Primitives": "(,10.0.32767]", + "Microsoft.Extensions.Validation": "(,10.0.32767]", + "Microsoft.Extensions.WebEncoders": "(,10.0.32767]", + "Microsoft.JSInterop": "(,10.0.32767]", + "Microsoft.Net.Http.Headers": "(,10.0.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.EventLog": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Cbor": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Cryptography.Xml": "(,10.0.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.RateLimiting": "(,10.0.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "projectName": "Jellyfin.CodeAnalysis", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "netstandard2.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "netstandard2.0": { + "targetAlias": "netstandard2.0", + "projectReferences": {} + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "netstandard2.0": { + "targetAlias": "netstandard2.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[3.11.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.CSharp": { + "suppressParent": "All", + "target": "Package", + "version": "[5.0.0, )", + "versionCentrallyManaged": true + }, + "NETStandard.Library": { + "suppressParent": "All", + "target": "Package", + "version": "[2.0.3, )", + "autoReferenced": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/RuntimeIdentifierGraph.json" + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj": { + "version": "10.11.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "projectName": "Jellyfin.Database.Implementations", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Design": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Relational": { + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Microsoft.EntityFrameworkCore.Tools": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[10.0.3, )", + "versionCentrallyManaged": true + }, + "Polly": { + "target": "Package", + "version": "[8.6.5, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "projectName": "Jellyfin.Extensions", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.Extensions/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "noWarn": [ + "NU5104" + ], + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "Diacritics": { + "target": "Package", + "version": "[4.1.4, )", + "versionCentrallyManaged": true + }, + "ICU4N.Transliterator": { + "target": "Package", + "version": "[60.1.0-alpha.356, )", + "versionCentrallyManaged": true + }, + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Common/obj/MediaBrowser.Common.csproj.nuget.g.props b/MediaBrowser.Common/obj/MediaBrowser.Common.csproj.nuget.g.props new file mode 100644 index 00000000..aa204e43 --- /dev/null +++ b/MediaBrowser.Common/obj/MediaBrowser.Common.csproj.nuget.g.props @@ -0,0 +1,26 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + /home/wjones/.nuget/packages/ + /home/wjones/.nuget/packages/ + PackageReference + 7.0.0 + + + + + + + + + + /home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556 + /home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31 + /home/wjones/.nuget/packages/seriloganalyzer/0.15.0 + /home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0 + /home/wjones/.nuget/packages/idisposableanalyzers/4.0.8 + + \ No newline at end of file diff --git a/MediaBrowser.Common/obj/MediaBrowser.Common.csproj.nuget.g.targets b/MediaBrowser.Common/obj/MediaBrowser.Common.csproj.nuget.g.targets new file mode 100644 index 00000000..43793a2f --- /dev/null +++ b/MediaBrowser.Common/obj/MediaBrowser.Common.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/MediaBrowser.Common/obj/project.assets.json b/MediaBrowser.Common/obj/project.assets.json new file mode 100644 index 00000000..bf23ae78 --- /dev/null +++ b/MediaBrowser.Common/obj/project.assets.json @@ -0,0 +1,1253 @@ +{ + "version": 3, + "targets": { + "net10.0": { + "Diacritics/4.1.4": { + "type": "package", + "compile": { + "lib/net9.0/Diacritics.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Diacritics.dll": { + "related": ".xml" + } + } + }, + "ICU4N/60.1.0-alpha.356": { + "type": "package", + "dependencies": { + "J2N": "2.0.0" + }, + "compile": { + "lib/netstandard2.0/ICU4N.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/ICU4N.dll": { + "related": ".xml" + } + } + }, + "ICU4N.Transliterator/60.1.0-alpha.356": { + "type": "package", + "dependencies": { + "ICU4N": "60.1.0-alpha.356" + }, + "compile": { + "lib/netstandard2.0/ICU4N.Transliterator.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/ICU4N.Transliterator.dll": { + "related": ".xml" + } + } + }, + "IDisposableAnalyzers/4.0.8": { + "type": "package" + }, + "J2N/2.0.0": { + "type": "package", + "compile": { + "lib/net6.0/J2N.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/J2N.dll": { + "related": ".xml" + } + } + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers/4.14.0": { + "type": "package", + "build": { + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props": {}, + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets": {} + } + }, + "Microsoft.EntityFrameworkCore/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "10.0.3", + "Microsoft.EntityFrameworkCore.Analyzers": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props": {} + } + }, + "Microsoft.EntityFrameworkCore.Abstractions/10.0.3": { + "type": "package", + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + } + }, + "Microsoft.EntityFrameworkCore.Analyzers/10.0.3": { + "type": "package" + }, + "Microsoft.EntityFrameworkCore.Relational/10.0.3": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore": "10.0.3" + }, + "compile": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + } + }, + "Polly/8.6.5": { + "type": "package", + "dependencies": { + "Polly.Core": "8.6.5" + }, + "compile": { + "lib/net6.0/Polly.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net6.0/Polly.dll": { + "related": ".pdb;.xml" + } + } + }, + "Polly.Core/8.6.5": { + "type": "package", + "compile": { + "lib/net8.0/Polly.Core.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net8.0/Polly.Core.dll": { + "related": ".pdb;.xml" + } + } + }, + "SerilogAnalyzer/0.15.0": { + "type": "package" + }, + "SmartAnalyzers.MultithreadingAnalyzer/1.1.31": { + "type": "package" + }, + "StyleCop.Analyzers/1.2.0-beta.556": { + "type": "package", + "dependencies": { + "StyleCop.Analyzers.Unstable": "1.2.0.556" + } + }, + "StyleCop.Analyzers.Unstable/1.2.0.556": { + "type": "package" + }, + "Jellyfin.CodeAnalysis/1.0.0": { + "type": "project", + "framework": ".NETStandard,Version=v2.0", + "compile": { + "bin/placeholder/Jellyfin.CodeAnalysis.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.CodeAnalysis.dll": {} + } + }, + "Jellyfin.Data/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Database.Implementations": "10.11.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Data.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Data.dll": {} + } + }, + "Jellyfin.Database.Implementations/10.11.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Polly": "8.6.5" + }, + "compile": { + "bin/placeholder/Jellyfin.Database.Implementations.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Database.Implementations.dll": {} + } + }, + "Jellyfin.Extensions/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Diacritics": "4.1.4", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "Jellyfin.CodeAnalysis": "1.0.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Extensions.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Extensions.dll": {} + } + }, + "Jellyfin.Model/10.12.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v10.0", + "dependencies": { + "Jellyfin.CodeAnalysis": "1.0.0", + "Jellyfin.Data": "10.12.0", + "Jellyfin.Extensions": "10.12.0" + }, + "compile": { + "bin/placeholder/Jellyfin.Model.dll": {} + }, + "runtime": { + "bin/placeholder/Jellyfin.Model.dll": {} + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + } + } + }, + "libraries": { + "Diacritics/4.1.4": { + "sha512": "9JK2bI+oWnzOK4Lu+5FXt7czofD28AMnZ95np5fkSClqioPnga1JVpjaUxVJaEpc/5HIlfikc6YI33lm2qu4mw==", + "type": "package", + "path": "diacritics/4.1.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "README.md", + "diacritics.4.1.4.nupkg.sha512", + "diacritics.nuspec", + "lib/net48/Diacritics.dll", + "lib/net48/Diacritics.xml", + "lib/net7.0/Diacritics.dll", + "lib/net7.0/Diacritics.xml", + "lib/net8.0/Diacritics.dll", + "lib/net8.0/Diacritics.xml", + "lib/net9.0/Diacritics.dll", + "lib/net9.0/Diacritics.xml", + "lib/netstandard1.2/Diacritics.dll", + "lib/netstandard1.2/Diacritics.xml", + "lib/netstandard2.0/Diacritics.dll", + "lib/netstandard2.0/Diacritics.xml", + "lib/netstandard2.1/Diacritics.dll", + "lib/netstandard2.1/Diacritics.xml", + "logo.png" + ] + }, + "ICU4N/60.1.0-alpha.356": { + "sha512": "YMZtDnjcqWzziOKiE7w6Ma7Rl5vuFDxzOsUlHh1QyfghbNEIZQOLRs9MMfwCWAjX6n9UitrF6vLXy55Z5q+4Fg==", + "type": "package", + "path": "icu4n/60.1.0-alpha.356", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "icu4n.60.1.0-alpha.356.nupkg.sha512", + "icu4n.nuspec", + "lib/net40/ICU4N.dll", + "lib/net40/ICU4N.xml", + "lib/net451/ICU4N.dll", + "lib/net451/ICU4N.xml", + "lib/netstandard2.0/ICU4N.dll", + "lib/netstandard2.0/ICU4N.xml", + "unicode-icon-128x128.jpg" + ] + }, + "ICU4N.Transliterator/60.1.0-alpha.356": { + "sha512": "lFOSO6bbEtB6HkWMNDJAq+rFwVyi9g6xVc5O/2xHa6iZnV7wLVDqCbaQ4W4vIeBSQZAafqhxciaEkmAvSdzlCg==", + "type": "package", + "path": "icu4n.transliterator/60.1.0-alpha.356", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "icu4n.transliterator.60.1.0-alpha.356.nupkg.sha512", + "icu4n.transliterator.nuspec", + "lib/net40/ICU4N.Transliterator.dll", + "lib/net40/ICU4N.Transliterator.xml", + "lib/net451/ICU4N.Transliterator.dll", + "lib/net451/ICU4N.Transliterator.xml", + "lib/netstandard2.0/ICU4N.Transliterator.dll", + "lib/netstandard2.0/ICU4N.Transliterator.xml", + "unicode-icon-128x128.jpg" + ] + }, + "IDisposableAnalyzers/4.0.8": { + "sha512": "vNi4NMG0CcJyjXxiNDcQ21FwV/whM9o9OEZKD+oP7tuxAqFEzX/x5OhC3OZJqW/w+8GOtCmJPBquYgMWgz0rfQ==", + "type": "package", + "path": "idisposableanalyzers/4.0.8", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/IDisposableAnalyzers.dll", + "idisposableanalyzers.4.0.8.nupkg.sha512", + "idisposableanalyzers.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "J2N/2.0.0": { + "sha512": "M5bwDajAARZiyqupU+rHQJnsVLxNBOHJ8vKYHd8LcLIb1FgLfzzcJvc31Qo5Xz/GEHFjDF9ScjKL/ks/zRTXuA==", + "type": "package", + "path": "j2n/2.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt", + "j2n-icon-100x100.png", + "j2n.2.0.0.nupkg.sha512", + "j2n.nuspec", + "lib/net40/J2N.dll", + "lib/net40/J2N.xml", + "lib/net45/J2N.dll", + "lib/net45/J2N.xml", + "lib/net5.0/J2N.dll", + "lib/net5.0/J2N.xml", + "lib/net6.0/J2N.dll", + "lib/net6.0/J2N.xml", + "lib/netcoreapp3.0/J2N.dll", + "lib/netcoreapp3.0/J2N.xml", + "lib/netstandard2.0/J2N.dll", + "lib/netstandard2.0/J2N.xml", + "lib/netstandard2.1/J2N.dll", + "lib/netstandard2.1/J2N.xml", + "readme.md" + ] + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers/4.14.0": { + "sha512": "gSWJlDWwmDhtbrEJGiHqvEjz9KthIiFD0qYB8zZ6a7z+xpMSPEtM9yTYELSa58iFWYlzSRqP9FXO6KoT3+ZMtg==", + "type": "package", + "path": "microsoft.codeanalysis.bannedapianalyzers/4.14.0", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.txt", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.dll", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.CSharp.BannedApiAnalyzers.dll", + "analyzers/dotnet/cs/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/de/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/es/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/fr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/it/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ja/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ko/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/pl/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/pt-BR/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/ru/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/tr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hans/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hant/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.BannedApiAnalyzers.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.VisualBasic.BannedApiAnalyzers.dll", + "analyzers/dotnet/vb/cs/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/de/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/es/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/fr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/it/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ja/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ko/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/pl/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/pt-BR/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/ru/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/tr/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hans/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hant/Microsoft.CodeAnalysis.BannedApiAnalyzers.resources.dll", + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props", + "buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets", + "documentation/Microsoft.CodeAnalysis.BannedApiAnalyzers.md", + "documentation/Microsoft.CodeAnalysis.BannedApiAnalyzers.sarif", + "editorconfig/AllRulesDefault/.editorconfig", + "editorconfig/AllRulesDisabled/.editorconfig", + "editorconfig/AllRulesEnabled/.editorconfig", + "editorconfig/ApiDesignRulesDefault/.editorconfig", + "editorconfig/ApiDesignRulesEnabled/.editorconfig", + "editorconfig/DataflowRulesDefault/.editorconfig", + "editorconfig/DataflowRulesEnabled/.editorconfig", + "editorconfig/PortedFromFxCopRulesDefault/.editorconfig", + "editorconfig/PortedFromFxCopRulesEnabled/.editorconfig", + "microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512", + "microsoft.codeanalysis.bannedapianalyzers.nuspec", + "rulesets/AllRulesDefault.ruleset", + "rulesets/AllRulesDisabled.ruleset", + "rulesets/AllRulesEnabled.ruleset", + "rulesets/ApiDesignRulesDefault.ruleset", + "rulesets/ApiDesignRulesEnabled.ruleset", + "rulesets/DataflowRulesDefault.ruleset", + "rulesets/DataflowRulesEnabled.ruleset", + "rulesets/PortedFromFxCopRulesDefault.ruleset", + "rulesets/PortedFromFxCopRulesEnabled.ruleset", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Microsoft.EntityFrameworkCore/10.0.3": { + "sha512": "CDEImwD4A7BseABJMCpLZnhfFjmPY/bHwhhS70elc6gLI/bYUEOhxWt7PmaNGYGhIEzOnStlCy5QcVb+8dod5Q==", + "type": "package", + "path": "microsoft.entityframeworkcore/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props", + "lib/net10.0/Microsoft.EntityFrameworkCore.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.xml", + "microsoft.entityframeworkcore.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Abstractions/10.0.3": { + "sha512": "c7Uoz381xnMHNBRB8eHRhGgzUtXbgddlbODhwZRrTSzZWDharp3RkJsFwhxyESbeXhCqmML7VdvjMQ7uu+HreA==", + "type": "package", + "path": "microsoft.entityframeworkcore.abstractions/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.xml", + "microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.abstractions.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Analyzers/10.0.3": { + "sha512": "lVABgJTyTUNE7Bi0bSu4dWHiCHIXEGzTh/kLh0N07IgU/tIKwTeBPp8tgV4x2iKj4h7iPLo8oXzyHmLDGtAE1g==", + "type": "package", + "path": "microsoft.entityframeworkcore.analyzers/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "analyzers/dotnet/cs/Microsoft.EntityFrameworkCore.Analyzers.dll", + "docs/PACKAGE.md", + "microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.analyzers.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Relational/10.0.3": { + "sha512": "Pmh60OK9neVr/M0FJwm9hlzm2bD4Kd65SID8E6SP5c90tExNgXwORrlEWl0oGU/ig9ifpNN4PSpIrnHNozlT5w==", + "type": "package", + "path": "microsoft.entityframeworkcore.relational/10.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll", + "lib/net10.0/Microsoft.EntityFrameworkCore.Relational.xml", + "microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512", + "microsoft.entityframeworkcore.relational.nuspec" + ] + }, + "Polly/8.6.5": { + "sha512": "VqtW2ZE/ALvQMAH1cQY3qZ2cF2OXa3oe/HKMdOv6Q02HCoEW0rsFNfcBONXlHBe1TnjWW1vdRxBEkPeq0/2FHA==", + "type": "package", + "path": "polly/8.6.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "lib/net462/Polly.dll", + "lib/net462/Polly.pdb", + "lib/net462/Polly.xml", + "lib/net472/Polly.dll", + "lib/net472/Polly.pdb", + "lib/net472/Polly.xml", + "lib/net6.0/Polly.dll", + "lib/net6.0/Polly.pdb", + "lib/net6.0/Polly.xml", + "lib/netstandard2.0/Polly.dll", + "lib/netstandard2.0/Polly.pdb", + "lib/netstandard2.0/Polly.xml", + "package-icon.png", + "package-readme.md", + "polly.8.6.5.nupkg.sha512", + "polly.nuspec" + ] + }, + "Polly.Core/8.6.5": { + "sha512": "t+sUVrIwvo7UmsgHGgOG9F0GDZSRIm47u2ylH17Gvcv1q5hNEwgD5GoBlFyc0kh/pebmPyrAgvGsR/65ZBaXlg==", + "type": "package", + "path": "polly.core/8.6.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "lib/net462/Polly.Core.dll", + "lib/net462/Polly.Core.pdb", + "lib/net462/Polly.Core.xml", + "lib/net472/Polly.Core.dll", + "lib/net472/Polly.Core.pdb", + "lib/net472/Polly.Core.xml", + "lib/net6.0/Polly.Core.dll", + "lib/net6.0/Polly.Core.pdb", + "lib/net6.0/Polly.Core.xml", + "lib/net8.0/Polly.Core.dll", + "lib/net8.0/Polly.Core.pdb", + "lib/net8.0/Polly.Core.xml", + "lib/netstandard2.0/Polly.Core.dll", + "lib/netstandard2.0/Polly.Core.pdb", + "lib/netstandard2.0/Polly.Core.xml", + "package-icon.png", + "package-readme.md", + "polly.core.8.6.5.nupkg.sha512", + "polly.core.nuspec" + ] + }, + "SerilogAnalyzer/0.15.0": { + "sha512": "sVpwfls4MfNnwIXLSGCgaUnV+c9kgJ8ia6GsyRcpd4Vs3gLogSDtSYBYrre2K2u/PNMo8GgG09RehwVnze70Tw==", + "type": "package", + "path": "seriloganalyzer/0.15.0", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/SerilogAnalyzer.dll", + "seriloganalyzer.0.15.0.nupkg.sha512", + "seriloganalyzer.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "SmartAnalyzers.MultithreadingAnalyzer/1.1.31": { + "sha512": "2f2k7bbhDd132ArglCKpzKoWBcp3uzbIFcb4aosnlqIKlfYKDE2HevBVRNVa+LkWFnjXFFWs47Bo96fu8iS//Q==", + "type": "package", + "path": "smartanalyzers.multithreadinganalyzer/1.1.31", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "analyzers/dotnet/cs/SmartAnalyzers.MultithreadingAnalyzer.dll", + "smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512", + "smartanalyzers.multithreadinganalyzer.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "StyleCop.Analyzers/1.2.0-beta.556": { + "sha512": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", + "type": "package", + "path": "stylecop.analyzers/1.2.0-beta.556", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "THIRD-PARTY-NOTICES.txt", + "stylecop.analyzers.1.2.0-beta.556.nupkg.sha512", + "stylecop.analyzers.nuspec" + ] + }, + "StyleCop.Analyzers.Unstable/1.2.0.556": { + "sha512": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==", + "type": "package", + "path": "stylecop.analyzers.unstable/1.2.0.556", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE", + "THIRD-PARTY-NOTICES.txt", + "analyzers/dotnet/cs/StyleCop.Analyzers.CodeFixes.dll", + "analyzers/dotnet/cs/StyleCop.Analyzers.dll", + "analyzers/dotnet/cs/de-DE/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/en-GB/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/es-MX/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/fr-FR/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/pl-PL/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/pt-BR/StyleCop.Analyzers.resources.dll", + "analyzers/dotnet/cs/ru-RU/StyleCop.Analyzers.resources.dll", + "rulesets/StyleCopAnalyzersDefault.ruleset", + "stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512", + "stylecop.analyzers.unstable.nuspec", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Jellyfin.CodeAnalysis/1.0.0": { + "type": "project", + "path": "../src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj", + "msbuildProject": "../src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + }, + "Jellyfin.Data/10.12.0": { + "type": "project", + "path": "../Jellyfin.Data/Jellyfin.Data.csproj", + "msbuildProject": "../Jellyfin.Data/Jellyfin.Data.csproj" + }, + "Jellyfin.Database.Implementations/10.11.0": { + "type": "project", + "path": "../src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj", + "msbuildProject": "../src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj" + }, + "Jellyfin.Extensions/10.12.0": { + "type": "project", + "path": "../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj", + "msbuildProject": "../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" + }, + "Jellyfin.Model/10.12.0": { + "type": "project", + "path": "../MediaBrowser.Model/MediaBrowser.Model.csproj", + "msbuildProject": "../MediaBrowser.Model/MediaBrowser.Model.csproj" + } + }, + "projectFileDependencyGroups": { + "net10.0": [ + "IDisposableAnalyzers >= 4.0.8", + "Jellyfin.CodeAnalysis >= 1.0.0", + "Jellyfin.Model >= 10.12.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers >= 4.14.0", + "SerilogAnalyzer >= 0.15.0", + "SmartAnalyzers.MultithreadingAnalyzer >= 1.1.31", + "StyleCop.Analyzers >= 1.2.0-beta.556" + ] + }, + "packageFolders": { + "/home/wjones/.nuget/packages/": {} + }, + "project": { + "version": "10.12.0", + "restore": { + "projectUniqueName": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj", + "projectName": "Jellyfin.Common", + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj", + "packagesPath": "/home/wjones/.nuget/packages/", + "outputPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/obj/", + "projectStyle": "PackageReference", + "centralPackageVersionsManagementEnabled": true, + "configFilePaths": [ + "/srv/common_drive/Projects/pgsql-jellyfin/nuget.config", + "/home/wjones/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net10.0" + ], + "sources": { + "/usr/lib/dotnet/library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "projectReferences": { + "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" + }, + "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj": { + "projectPath": "/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" + } + } + } + }, + "warningProperties": { + "allWarningsAsErrors": true, + "warnAsError": [ + "NU1605" + ], + "warnNotAsError": [ + "NU1902", + "NU1903" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "all" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "net10.0": { + "targetAlias": "net10.0", + "dependencies": { + "IDisposableAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.0.8, )", + "versionCentrallyManaged": true + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers", + "suppressParent": "All", + "target": "Package", + "version": "[4.14.0, )", + "versionCentrallyManaged": true + }, + "SerilogAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[0.15.0, )", + "versionCentrallyManaged": true + }, + "SmartAnalyzers.MultithreadingAnalyzer": { + "suppressParent": "All", + "target": "Package", + "version": "[1.1.31, )", + "versionCentrallyManaged": true + }, + "StyleCop.Analyzers": { + "suppressParent": "All", + "target": "Package", + "version": "[1.2.0-beta.556, )", + "versionCentrallyManaged": true + } + }, + "centralPackageVersions": { + "AsyncKeyedLock": "8.0.2", + "AutoFixture": "4.18.1", + "AutoFixture.AutoMoq": "4.18.1", + "AutoFixture.Xunit2": "4.18.1", + "BDInfo": "0.8.0", + "BitFaster.Caching": "2.5.4", + "BlurHashSharp": "1.4.0-pre.1", + "BlurHashSharp.SkiaSharp": "1.4.0-pre.1", + "CommandLineParser": "2.9.1", + "coverlet.collector": "8.0.0", + "Diacritics": "4.1.4", + "DiscUtils.Udf": "0.16.13", + "DotNet.Glob": "3.1.3", + "FsCheck.Xunit": "3.3.2", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "ICU4N.Transliterator": "60.1.0-alpha.356", + "IDisposableAnalyzers": "4.0.8", + "Ignore": "0.2.1", + "Jellyfin.XmlTv": "10.8.0", + "libse": "4.0.12", + "LrcParser": "2025.623.0", + "MetaBrainz.MusicBrainz": "8.0.1", + "Microsoft.AspNetCore.Authorization": "10.0.3", + "Microsoft.AspNetCore.Mvc.Testing": "10.0.3", + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "4.14.0", + "Microsoft.CodeAnalysis.Common": "5.0.0", + "Microsoft.CodeAnalysis.CSharp": "5.0.0", + "Microsoft.Data.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Design": "10.0.3", + "Microsoft.EntityFrameworkCore.Relational": "10.0.3", + "Microsoft.EntityFrameworkCore.Sqlite": "10.0.3", + "Microsoft.EntityFrameworkCore.Tools": "10.0.3", + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.Caching.Memory": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "10.0.3", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", + "Microsoft.Extensions.Http": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.NET.Test.Sdk": "18.0.1", + "MimeTypes": "2.5.2", + "Moq": "4.18.4", + "Morestachio": "5.0.1.631", + "NEbml": "1.1.0.5", + "Newtonsoft.Json": "13.0.4", + "PlaylistsNET": "1.4.1", + "Polly": "8.6.5", + "prometheus-net": "8.2.1", + "prometheus-net.AspNetCore": "8.2.1", + "prometheus-net.DotNetRuntime": "4.4.1", + "Serilog.AspNetCore": "10.0.0", + "Serilog.Enrichers.Thread": "4.0.0", + "Serilog.Expressions": "5.0.0", + "Serilog.Settings.Configuration": "10.0.0", + "Serilog.Sinks.Async": "2.1.0", + "Serilog.Sinks.Console": "6.1.1", + "Serilog.Sinks.File": "7.0.0", + "Serilog.Sinks.Graylog": "3.1.1", + "SerilogAnalyzer": "0.15.0", + "SharpFuzz": "2.2.0", + "SkiaSharp": "[3.116.1]", + "SkiaSharp.HarfBuzz": "[3.116.1]", + "SkiaSharp.NativeAssets.Linux": "[3.116.1]", + "SmartAnalyzers.MultithreadingAnalyzer": "1.1.31", + "StyleCop.Analyzers": "1.2.0-beta.556", + "Svg.Skia": "3.4.1", + "Swashbuckle.AspNetCore": "7.3.2", + "Swashbuckle.AspNetCore.ReDoc": "6.9.0", + "System.Text.Json": "10.0.3", + "TagLibSharp": "2.3.0", + "TMDbLib": "2.3.0", + "UTF.Unknown": "2.6.0", + "xunit": "2.9.3", + "Xunit.Priority": "1.1.6", + "xunit.runner.visualstudio": "2.8.2", + "Xunit.SkippableFact": "1.5.61", + "z440.atl.core": "7.11.0" + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json", + "packagesToPrune": { + "Microsoft.AspNetCore": "(,10.0.32767]", + "Microsoft.AspNetCore.Antiforgery": "(,10.0.32767]", + "Microsoft.AspNetCore.App": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.BearerToken": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Cookies": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Authentication.OAuth": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Authorization.Policy": "(,10.0.32767]", + "Microsoft.AspNetCore.Components": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Authorization": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Endpoints": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Forms": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Server": "(,10.0.32767]", + "Microsoft.AspNetCore.Components.Web": "(,10.0.32767]", + "Microsoft.AspNetCore.Connections.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.CookiePolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.Internal": "(,10.0.32767]", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.DataProtection.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.AspNetCore.HostFiltering": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Html.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Connections.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Extensions": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Features": "(,10.0.32767]", + "Microsoft.AspNetCore.Http.Results": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpLogging": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpOverrides": "(,10.0.32767]", + "Microsoft.AspNetCore.HttpsPolicy": "(,10.0.32767]", + "Microsoft.AspNetCore.Identity": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Localization.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Metadata": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ApiExplorer": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Cors": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.DataAnnotations": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Formatters.Xml": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Localization": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.RazorPages": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.TagHelpers": "(,10.0.32767]", + "Microsoft.AspNetCore.Mvc.ViewFeatures": "(,10.0.32767]", + "Microsoft.AspNetCore.OutputCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.RateLimiting": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor": "(,10.0.32767]", + "Microsoft.AspNetCore.Razor.Runtime": "(,10.0.32767]", + "Microsoft.AspNetCore.RequestDecompression": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.ResponseCompression": "(,10.0.32767]", + "Microsoft.AspNetCore.Rewrite": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing": "(,10.0.32767]", + "Microsoft.AspNetCore.Routing.Abstractions": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.HttpSys": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IIS": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.IISIntegration": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic": "(,10.0.32767]", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets": "(,10.0.32767]", + "Microsoft.AspNetCore.Session": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Common": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Core": "(,10.0.32767]", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticAssets": "(,10.0.32767]", + "Microsoft.AspNetCore.StaticFiles": "(,10.0.32767]", + "Microsoft.AspNetCore.WebSockets": "(,10.0.32767]", + "Microsoft.AspNetCore.WebUtilities": "(,10.0.32767]", + "Microsoft.CSharp": "(,4.7.32767]", + "Microsoft.Extensions.Caching.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Caching.Memory": "(,10.0.32767]", + "Microsoft.Extensions.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Binder": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.CommandLine": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.FileExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Ini": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Json": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.KeyPerFile": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.UserSecrets": "(,10.0.32767]", + "Microsoft.Extensions.Configuration.Xml": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection": "(,10.0.32767]", + "Microsoft.Extensions.DependencyInjection.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks": "(,10.0.32767]", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Features": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Composite": "(,10.0.32767]", + "Microsoft.Extensions.FileProviders.Physical": "(,10.0.32767]", + "Microsoft.Extensions.FileSystemGlobbing": "(,10.0.32767]", + "Microsoft.Extensions.Hosting": "(,10.0.32767]", + "Microsoft.Extensions.Hosting.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Http": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Core": "(,10.0.32767]", + "Microsoft.Extensions.Identity.Stores": "(,10.0.32767]", + "Microsoft.Extensions.Localization": "(,10.0.32767]", + "Microsoft.Extensions.Localization.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Abstractions": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Configuration": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Console": "(,10.0.32767]", + "Microsoft.Extensions.Logging.Debug": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventLog": "(,10.0.32767]", + "Microsoft.Extensions.Logging.EventSource": "(,10.0.32767]", + "Microsoft.Extensions.Logging.TraceSource": "(,10.0.32767]", + "Microsoft.Extensions.ObjectPool": "(,10.0.32767]", + "Microsoft.Extensions.Options": "(,10.0.32767]", + "Microsoft.Extensions.Options.ConfigurationExtensions": "(,10.0.32767]", + "Microsoft.Extensions.Options.DataAnnotations": "(,10.0.32767]", + "Microsoft.Extensions.Primitives": "(,10.0.32767]", + "Microsoft.Extensions.Validation": "(,10.0.32767]", + "Microsoft.Extensions.WebEncoders": "(,10.0.32767]", + "Microsoft.JSInterop": "(,10.0.32767]", + "Microsoft.Net.Http.Headers": "(,10.0.32767]", + "Microsoft.VisualBasic": "(,10.4.32767]", + "Microsoft.Win32.Primitives": "(,4.3.32767]", + "Microsoft.Win32.Registry": "(,5.0.32767]", + "runtime.any.System.Collections": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.any.System.Globalization": "(,4.3.32767]", + "runtime.any.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.any.System.IO": "(,4.3.32767]", + "runtime.any.System.Reflection": "(,4.3.32767]", + "runtime.any.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.any.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.any.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.any.System.Runtime": "(,4.3.32767]", + "runtime.any.System.Runtime.Handles": "(,4.3.32767]", + "runtime.any.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.any.System.Text.Encoding": "(,4.3.32767]", + "runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.any.System.Threading.Tasks": "(,4.3.32767]", + "runtime.any.System.Threading.Timer": "(,4.3.32767]", + "runtime.aot.System.Collections": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]", + "runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]", + "runtime.aot.System.Globalization": "(,4.3.32767]", + "runtime.aot.System.Globalization.Calendars": "(,4.3.32767]", + "runtime.aot.System.IO": "(,4.3.32767]", + "runtime.aot.System.Reflection": "(,4.3.32767]", + "runtime.aot.System.Reflection.Extensions": "(,4.3.32767]", + "runtime.aot.System.Reflection.Primitives": "(,4.3.32767]", + "runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]", + "runtime.aot.System.Runtime": "(,4.3.32767]", + "runtime.aot.System.Runtime.Handles": "(,4.3.32767]", + "runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding": "(,4.3.32767]", + "runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]", + "runtime.aot.System.Threading.Tasks": "(,4.3.32767]", + "runtime.aot.System.Threading.Timer": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]", + "runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]", + "runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.unix.System.Console": "(,4.3.32767]", + "runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.unix.System.IO.FileSystem": "(,4.3.32767]", + "runtime.unix.System.Net.Primitives": "(,4.3.32767]", + "runtime.unix.System.Net.Sockets": "(,4.3.32767]", + "runtime.unix.System.Private.Uri": "(,4.3.32767]", + "runtime.unix.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]", + "runtime.win.System.Console": "(,4.3.32767]", + "runtime.win.System.Diagnostics.Debug": "(,4.3.32767]", + "runtime.win.System.IO.FileSystem": "(,4.3.32767]", + "runtime.win.System.Net.Primitives": "(,4.3.32767]", + "runtime.win.System.Net.Sockets": "(,4.3.32767]", + "runtime.win.System.Runtime.Extensions": "(,4.3.32767]", + "runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]", + "runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]", + "runtime.win7.System.Private.Uri": "(,4.3.32767]", + "runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]", + "System.AppContext": "(,4.3.32767]", + "System.Buffers": "(,5.0.32767]", + "System.Collections": "(,4.3.32767]", + "System.Collections.Concurrent": "(,4.3.32767]", + "System.Collections.Immutable": "(,10.0.32767]", + "System.Collections.NonGeneric": "(,4.3.32767]", + "System.Collections.Specialized": "(,4.3.32767]", + "System.ComponentModel": "(,4.3.32767]", + "System.ComponentModel.Annotations": "(,4.3.32767]", + "System.ComponentModel.EventBasedAsync": "(,4.3.32767]", + "System.ComponentModel.Primitives": "(,4.3.32767]", + "System.ComponentModel.TypeConverter": "(,4.3.32767]", + "System.Console": "(,4.3.32767]", + "System.Data.Common": "(,4.3.32767]", + "System.Data.DataSetExtensions": "(,4.4.32767]", + "System.Diagnostics.Contracts": "(,4.3.32767]", + "System.Diagnostics.Debug": "(,4.3.32767]", + "System.Diagnostics.DiagnosticSource": "(,10.0.32767]", + "System.Diagnostics.EventLog": "(,10.0.32767]", + "System.Diagnostics.FileVersionInfo": "(,4.3.32767]", + "System.Diagnostics.Process": "(,4.3.32767]", + "System.Diagnostics.StackTrace": "(,4.3.32767]", + "System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]", + "System.Diagnostics.Tools": "(,4.3.32767]", + "System.Diagnostics.TraceSource": "(,4.3.32767]", + "System.Diagnostics.Tracing": "(,4.3.32767]", + "System.Drawing.Primitives": "(,4.3.32767]", + "System.Dynamic.Runtime": "(,4.3.32767]", + "System.Formats.Asn1": "(,10.0.32767]", + "System.Formats.Cbor": "(,10.0.32767]", + "System.Formats.Tar": "(,10.0.32767]", + "System.Globalization": "(,4.3.32767]", + "System.Globalization.Calendars": "(,4.3.32767]", + "System.Globalization.Extensions": "(,4.3.32767]", + "System.IO": "(,4.3.32767]", + "System.IO.Compression": "(,4.3.32767]", + "System.IO.Compression.ZipFile": "(,4.3.32767]", + "System.IO.FileSystem": "(,4.3.32767]", + "System.IO.FileSystem.AccessControl": "(,4.4.32767]", + "System.IO.FileSystem.DriveInfo": "(,4.3.32767]", + "System.IO.FileSystem.Primitives": "(,4.3.32767]", + "System.IO.FileSystem.Watcher": "(,4.3.32767]", + "System.IO.IsolatedStorage": "(,4.3.32767]", + "System.IO.MemoryMappedFiles": "(,4.3.32767]", + "System.IO.Pipelines": "(,10.0.32767]", + "System.IO.Pipes": "(,4.3.32767]", + "System.IO.Pipes.AccessControl": "(,5.0.32767]", + "System.IO.UnmanagedMemoryStream": "(,4.3.32767]", + "System.Linq": "(,4.3.32767]", + "System.Linq.AsyncEnumerable": "(,10.0.32767]", + "System.Linq.Expressions": "(,4.3.32767]", + "System.Linq.Parallel": "(,4.3.32767]", + "System.Linq.Queryable": "(,4.3.32767]", + "System.Memory": "(,5.0.32767]", + "System.Net.Http": "(,4.3.32767]", + "System.Net.Http.Json": "(,10.0.32767]", + "System.Net.NameResolution": "(,4.3.32767]", + "System.Net.NetworkInformation": "(,4.3.32767]", + "System.Net.Ping": "(,4.3.32767]", + "System.Net.Primitives": "(,4.3.32767]", + "System.Net.Requests": "(,4.3.32767]", + "System.Net.Security": "(,4.3.32767]", + "System.Net.ServerSentEvents": "(,10.0.32767]", + "System.Net.Sockets": "(,4.3.32767]", + "System.Net.WebHeaderCollection": "(,4.3.32767]", + "System.Net.WebSockets": "(,4.3.32767]", + "System.Net.WebSockets.Client": "(,4.3.32767]", + "System.Numerics.Vectors": "(,5.0.32767]", + "System.ObjectModel": "(,4.3.32767]", + "System.Private.DataContractSerialization": "(,4.3.32767]", + "System.Private.Uri": "(,4.3.32767]", + "System.Reflection": "(,4.3.32767]", + "System.Reflection.DispatchProxy": "(,6.0.32767]", + "System.Reflection.Emit": "(,4.7.32767]", + "System.Reflection.Emit.ILGeneration": "(,4.7.32767]", + "System.Reflection.Emit.Lightweight": "(,4.7.32767]", + "System.Reflection.Extensions": "(,4.3.32767]", + "System.Reflection.Metadata": "(,10.0.32767]", + "System.Reflection.Primitives": "(,4.3.32767]", + "System.Reflection.TypeExtensions": "(,4.3.32767]", + "System.Resources.Reader": "(,4.3.32767]", + "System.Resources.ResourceManager": "(,4.3.32767]", + "System.Resources.Writer": "(,4.3.32767]", + "System.Runtime": "(,4.3.32767]", + "System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]", + "System.Runtime.CompilerServices.VisualC": "(,4.3.32767]", + "System.Runtime.Extensions": "(,4.3.32767]", + "System.Runtime.Handles": "(,4.3.32767]", + "System.Runtime.InteropServices": "(,4.3.32767]", + "System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]", + "System.Runtime.Loader": "(,4.3.32767]", + "System.Runtime.Numerics": "(,4.3.32767]", + "System.Runtime.Serialization.Formatters": "(,4.3.32767]", + "System.Runtime.Serialization.Json": "(,4.3.32767]", + "System.Runtime.Serialization.Primitives": "(,4.3.32767]", + "System.Runtime.Serialization.Xml": "(,4.3.32767]", + "System.Security.AccessControl": "(,6.0.32767]", + "System.Security.Claims": "(,4.3.32767]", + "System.Security.Cryptography.Algorithms": "(,4.3.32767]", + "System.Security.Cryptography.Cng": "(,5.0.32767]", + "System.Security.Cryptography.Csp": "(,4.3.32767]", + "System.Security.Cryptography.Encoding": "(,4.3.32767]", + "System.Security.Cryptography.OpenSsl": "(,5.0.32767]", + "System.Security.Cryptography.Primitives": "(,4.3.32767]", + "System.Security.Cryptography.X509Certificates": "(,4.3.32767]", + "System.Security.Cryptography.Xml": "(,10.0.32767]", + "System.Security.Principal": "(,4.3.32767]", + "System.Security.Principal.Windows": "(,5.0.32767]", + "System.Security.SecureString": "(,4.3.32767]", + "System.Text.Encoding": "(,4.3.32767]", + "System.Text.Encoding.CodePages": "(,10.0.32767]", + "System.Text.Encoding.Extensions": "(,4.3.32767]", + "System.Text.Encodings.Web": "(,10.0.32767]", + "System.Text.Json": "(,10.0.32767]", + "System.Text.RegularExpressions": "(,4.3.32767]", + "System.Threading": "(,4.3.32767]", + "System.Threading.AccessControl": "(,10.0.32767]", + "System.Threading.Channels": "(,10.0.32767]", + "System.Threading.Overlapped": "(,4.3.32767]", + "System.Threading.RateLimiting": "(,10.0.32767]", + "System.Threading.Tasks": "(,4.3.32767]", + "System.Threading.Tasks.Dataflow": "(,10.0.32767]", + "System.Threading.Tasks.Extensions": "(,5.0.32767]", + "System.Threading.Tasks.Parallel": "(,4.3.32767]", + "System.Threading.Thread": "(,4.3.32767]", + "System.Threading.ThreadPool": "(,4.3.32767]", + "System.Threading.Timer": "(,4.3.32767]", + "System.ValueTuple": "(,4.5.32767]", + "System.Xml.ReaderWriter": "(,4.3.32767]", + "System.Xml.XDocument": "(,4.3.32767]", + "System.Xml.XmlDocument": "(,4.3.32767]", + "System.Xml.XmlSerializer": "(,4.3.32767]", + "System.Xml.XPath": "(,4.3.32767]", + "System.Xml.XPath.XDocument": "(,5.0.32767]" + } + } + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Common/obj/project.nuget.cache b/MediaBrowser.Common/obj/project.nuget.cache new file mode 100644 index 00000000..ff69e057 --- /dev/null +++ b/MediaBrowser.Common/obj/project.nuget.cache @@ -0,0 +1,25 @@ +{ + "version": 2, + "dgSpecHash": "SvTROcOUqzw=", + "success": true, + "projectFilePath": "/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj", + "expectedPackageFiles": [ + "/home/wjones/.nuget/packages/diacritics/4.1.4/diacritics.4.1.4.nupkg.sha512", + "/home/wjones/.nuget/packages/icu4n/60.1.0-alpha.356/icu4n.60.1.0-alpha.356.nupkg.sha512", + "/home/wjones/.nuget/packages/icu4n.transliterator/60.1.0-alpha.356/icu4n.transliterator.60.1.0-alpha.356.nupkg.sha512", + "/home/wjones/.nuget/packages/idisposableanalyzers/4.0.8/idisposableanalyzers.4.0.8.nupkg.sha512", + "/home/wjones/.nuget/packages/j2n/2.0.0/j2n.2.0.0.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0/microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore/10.0.3/microsoft.entityframeworkcore.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.abstractions/10.0.3/microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.analyzers/10.0.3/microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/microsoft.entityframeworkcore.relational/10.0.3/microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512", + "/home/wjones/.nuget/packages/polly/8.6.5/polly.8.6.5.nupkg.sha512", + "/home/wjones/.nuget/packages/polly.core/8.6.5/polly.core.8.6.5.nupkg.sha512", + "/home/wjones/.nuget/packages/seriloganalyzer/0.15.0/seriloganalyzer.0.15.0.nupkg.sha512", + "/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31/smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512", + "/home/wjones/.nuget/packages/stylecop.analyzers/1.2.0-beta.556/stylecop.analyzers.1.2.0-beta.556.nupkg.sha512", + "/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556/stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/MediaBrowser.Common/obj/project.packagespec.json b/MediaBrowser.Common/obj/project.packagespec.json new file mode 100644 index 00000000..b7adaaed --- /dev/null +++ b/MediaBrowser.Common/obj/project.packagespec.json @@ -0,0 +1 @@ +"restore":{"projectUniqueName":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj","projectName":"Jellyfin.Common","projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/MediaBrowser.Common.csproj","outputPath":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Common/obj/","projectStyle":"PackageReference","centralPackageVersionsManagementEnabled":true,"originalTargetFrameworks":["net10.0"],"sources":{"/usr/lib/dotnet/library-packs":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net10.0":{"targetAlias":"net10.0","projectReferences":{"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj"},"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj":{"projectPath":"/srv/common_drive/Projects/pgsql-jellyfin/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj"}}}},"warningProperties":{"allWarningsAsErrors":true,"warnAsError":["NU1605"],"warnNotAsError":["NU1902","NU1903"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"all"},"SdkAnalysisLevel":"10.0.100"}"frameworks":{"net10.0":{"targetAlias":"net10.0","dependencies":{"IDisposableAnalyzers":{"include":"Runtime, Build, Native, ContentFiles, Analyzers","suppressParent":"All","target":"Package","version":"[4.0.8, )","versionCentrallyManaged":true},"Microsoft.CodeAnalysis.BannedApiAnalyzers":{"include":"Runtime, Build, Native, ContentFiles, Analyzers","suppressParent":"All","target":"Package","version":"[4.14.0, )","versionCentrallyManaged":true},"SerilogAnalyzer":{"suppressParent":"All","target":"Package","version":"[0.15.0, )","versionCentrallyManaged":true},"SmartAnalyzers.MultithreadingAnalyzer":{"suppressParent":"All","target":"Package","version":"[1.1.31, )","versionCentrallyManaged":true},"StyleCop.Analyzers":{"suppressParent":"All","target":"Package","version":"[1.2.0-beta.556, )","versionCentrallyManaged":true}},"centralPackageVersions":{"AsyncKeyedLock":"8.0.2","AutoFixture":"4.18.1","AutoFixture.AutoMoq":"4.18.1","AutoFixture.Xunit2":"4.18.1","BDInfo":"0.8.0","BitFaster.Caching":"2.5.4","BlurHashSharp":"1.4.0-pre.1","BlurHashSharp.SkiaSharp":"1.4.0-pre.1","CommandLineParser":"2.9.1","coverlet.collector":"8.0.0","Diacritics":"4.1.4","DiscUtils.Udf":"0.16.13","DotNet.Glob":"3.1.3","FsCheck.Xunit":"3.3.2","HarfBuzzSharp.NativeAssets.Linux":"8.3.1.1","ICU4N.Transliterator":"60.1.0-alpha.356","IDisposableAnalyzers":"4.0.8","Ignore":"0.2.1","Jellyfin.XmlTv":"10.8.0","libse":"4.0.12","LrcParser":"2025.623.0","MetaBrainz.MusicBrainz":"8.0.1","Microsoft.AspNetCore.Authorization":"10.0.3","Microsoft.AspNetCore.Mvc.Testing":"10.0.3","Microsoft.CodeAnalysis.Analyzers":"3.11.0","Microsoft.CodeAnalysis.BannedApiAnalyzers":"4.14.0","Microsoft.CodeAnalysis.Common":"5.0.0","Microsoft.CodeAnalysis.CSharp":"5.0.0","Microsoft.Data.Sqlite":"10.0.3","Microsoft.EntityFrameworkCore.Design":"10.0.3","Microsoft.EntityFrameworkCore.Relational":"10.0.3","Microsoft.EntityFrameworkCore.Sqlite":"10.0.3","Microsoft.EntityFrameworkCore.Tools":"10.0.3","Microsoft.Extensions.Caching.Abstractions":"10.0.3","Microsoft.Extensions.Caching.Memory":"10.0.3","Microsoft.Extensions.Configuration.Abstractions":"10.0.3","Microsoft.Extensions.Configuration.Binder":"10.0.3","Microsoft.Extensions.DependencyInjection":"10.0.3","Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore":"10.0.3","Microsoft.Extensions.Hosting.Abstractions":"10.0.3","Microsoft.Extensions.Http":"10.0.3","Microsoft.Extensions.Logging":"10.0.3","Microsoft.Extensions.Options":"10.0.3","Microsoft.NET.Test.Sdk":"18.0.1","MimeTypes":"2.5.2","Moq":"4.18.4","Morestachio":"5.0.1.631","NEbml":"1.1.0.5","Newtonsoft.Json":"13.0.4","PlaylistsNET":"1.4.1","Polly":"8.6.5","prometheus-net":"8.2.1","prometheus-net.AspNetCore":"8.2.1","prometheus-net.DotNetRuntime":"4.4.1","Serilog.AspNetCore":"10.0.0","Serilog.Enrichers.Thread":"4.0.0","Serilog.Expressions":"5.0.0","Serilog.Settings.Configuration":"10.0.0","Serilog.Sinks.Async":"2.1.0","Serilog.Sinks.Console":"6.1.1","Serilog.Sinks.File":"7.0.0","Serilog.Sinks.Graylog":"3.1.1","SerilogAnalyzer":"0.15.0","SharpFuzz":"2.2.0","SkiaSharp":"[3.116.1]","SkiaSharp.HarfBuzz":"[3.116.1]","SkiaSharp.NativeAssets.Linux":"[3.116.1]","SmartAnalyzers.MultithreadingAnalyzer":"1.1.31","StyleCop.Analyzers":"1.2.0-beta.556","Svg.Skia":"3.4.1","Swashbuckle.AspNetCore":"7.3.2","Swashbuckle.AspNetCore.ReDoc":"6.9.0","System.Text.Json":"10.0.3","TagLibSharp":"2.3.0","TMDbLib":"2.3.0","UTF.Unknown":"2.6.0","xunit":"2.9.3","Xunit.Priority":"1.1.6","xunit.runner.visualstudio":"2.8.2","Xunit.SkippableFact":"1.5.61","z440.atl.core":"7.11.0"},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.AspNetCore.App":{"privateAssets":"none"},"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"/usr/lib/dotnet/sdk/10.0.103/PortableRuntimeIdentifierGraph.json","packagesToPrune":{"Microsoft.AspNetCore":"(,10.0.32767]","Microsoft.AspNetCore.Antiforgery":"(,10.0.32767]","Microsoft.AspNetCore.App":"(,10.0.32767]","Microsoft.AspNetCore.Authentication":"(,10.0.32767]","Microsoft.AspNetCore.Authentication.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.Authentication.BearerToken":"(,10.0.32767]","Microsoft.AspNetCore.Authentication.Cookies":"(,10.0.32767]","Microsoft.AspNetCore.Authentication.Core":"(,10.0.32767]","Microsoft.AspNetCore.Authentication.OAuth":"(,10.0.32767]","Microsoft.AspNetCore.Authorization":"(,10.0.32767]","Microsoft.AspNetCore.Authorization.Policy":"(,10.0.32767]","Microsoft.AspNetCore.Components":"(,10.0.32767]","Microsoft.AspNetCore.Components.Authorization":"(,10.0.32767]","Microsoft.AspNetCore.Components.Endpoints":"(,10.0.32767]","Microsoft.AspNetCore.Components.Forms":"(,10.0.32767]","Microsoft.AspNetCore.Components.Server":"(,10.0.32767]","Microsoft.AspNetCore.Components.Web":"(,10.0.32767]","Microsoft.AspNetCore.Connections.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.CookiePolicy":"(,10.0.32767]","Microsoft.AspNetCore.Cors":"(,10.0.32767]","Microsoft.AspNetCore.Cryptography.Internal":"(,10.0.32767]","Microsoft.AspNetCore.Cryptography.KeyDerivation":"(,10.0.32767]","Microsoft.AspNetCore.DataProtection":"(,10.0.32767]","Microsoft.AspNetCore.DataProtection.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.DataProtection.Extensions":"(,10.0.32767]","Microsoft.AspNetCore.Diagnostics":"(,10.0.32767]","Microsoft.AspNetCore.Diagnostics.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.Diagnostics.HealthChecks":"(,10.0.32767]","Microsoft.AspNetCore.HostFiltering":"(,10.0.32767]","Microsoft.AspNetCore.Hosting":"(,10.0.32767]","Microsoft.AspNetCore.Hosting.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.Hosting.Server.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.Html.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.Http":"(,10.0.32767]","Microsoft.AspNetCore.Http.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.Http.Connections":"(,10.0.32767]","Microsoft.AspNetCore.Http.Connections.Common":"(,10.0.32767]","Microsoft.AspNetCore.Http.Extensions":"(,10.0.32767]","Microsoft.AspNetCore.Http.Features":"(,10.0.32767]","Microsoft.AspNetCore.Http.Results":"(,10.0.32767]","Microsoft.AspNetCore.HttpLogging":"(,10.0.32767]","Microsoft.AspNetCore.HttpOverrides":"(,10.0.32767]","Microsoft.AspNetCore.HttpsPolicy":"(,10.0.32767]","Microsoft.AspNetCore.Identity":"(,10.0.32767]","Microsoft.AspNetCore.Localization":"(,10.0.32767]","Microsoft.AspNetCore.Localization.Routing":"(,10.0.32767]","Microsoft.AspNetCore.Metadata":"(,10.0.32767]","Microsoft.AspNetCore.Mvc":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.ApiExplorer":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.Core":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.Cors":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.DataAnnotations":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.Formatters.Json":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.Formatters.Xml":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.Localization":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.Razor":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.RazorPages":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.TagHelpers":"(,10.0.32767]","Microsoft.AspNetCore.Mvc.ViewFeatures":"(,10.0.32767]","Microsoft.AspNetCore.OutputCaching":"(,10.0.32767]","Microsoft.AspNetCore.RateLimiting":"(,10.0.32767]","Microsoft.AspNetCore.Razor":"(,10.0.32767]","Microsoft.AspNetCore.Razor.Runtime":"(,10.0.32767]","Microsoft.AspNetCore.RequestDecompression":"(,10.0.32767]","Microsoft.AspNetCore.ResponseCaching":"(,10.0.32767]","Microsoft.AspNetCore.ResponseCaching.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.ResponseCompression":"(,10.0.32767]","Microsoft.AspNetCore.Rewrite":"(,10.0.32767]","Microsoft.AspNetCore.Routing":"(,10.0.32767]","Microsoft.AspNetCore.Routing.Abstractions":"(,10.0.32767]","Microsoft.AspNetCore.Server.HttpSys":"(,10.0.32767]","Microsoft.AspNetCore.Server.IIS":"(,10.0.32767]","Microsoft.AspNetCore.Server.IISIntegration":"(,10.0.32767]","Microsoft.AspNetCore.Server.Kestrel":"(,10.0.32767]","Microsoft.AspNetCore.Server.Kestrel.Core":"(,10.0.32767]","Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes":"(,10.0.32767]","Microsoft.AspNetCore.Server.Kestrel.Transport.Quic":"(,10.0.32767]","Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets":"(,10.0.32767]","Microsoft.AspNetCore.Session":"(,10.0.32767]","Microsoft.AspNetCore.SignalR":"(,10.0.32767]","Microsoft.AspNetCore.SignalR.Common":"(,10.0.32767]","Microsoft.AspNetCore.SignalR.Core":"(,10.0.32767]","Microsoft.AspNetCore.SignalR.Protocols.Json":"(,10.0.32767]","Microsoft.AspNetCore.StaticAssets":"(,10.0.32767]","Microsoft.AspNetCore.StaticFiles":"(,10.0.32767]","Microsoft.AspNetCore.WebSockets":"(,10.0.32767]","Microsoft.AspNetCore.WebUtilities":"(,10.0.32767]","Microsoft.CSharp":"(,4.7.32767]","Microsoft.Extensions.Caching.Abstractions":"(,10.0.32767]","Microsoft.Extensions.Caching.Memory":"(,10.0.32767]","Microsoft.Extensions.Configuration":"(,10.0.32767]","Microsoft.Extensions.Configuration.Abstractions":"(,10.0.32767]","Microsoft.Extensions.Configuration.Binder":"(,10.0.32767]","Microsoft.Extensions.Configuration.CommandLine":"(,10.0.32767]","Microsoft.Extensions.Configuration.EnvironmentVariables":"(,10.0.32767]","Microsoft.Extensions.Configuration.FileExtensions":"(,10.0.32767]","Microsoft.Extensions.Configuration.Ini":"(,10.0.32767]","Microsoft.Extensions.Configuration.Json":"(,10.0.32767]","Microsoft.Extensions.Configuration.KeyPerFile":"(,10.0.32767]","Microsoft.Extensions.Configuration.UserSecrets":"(,10.0.32767]","Microsoft.Extensions.Configuration.Xml":"(,10.0.32767]","Microsoft.Extensions.DependencyInjection":"(,10.0.32767]","Microsoft.Extensions.DependencyInjection.Abstractions":"(,10.0.32767]","Microsoft.Extensions.Diagnostics":"(,10.0.32767]","Microsoft.Extensions.Diagnostics.Abstractions":"(,10.0.32767]","Microsoft.Extensions.Diagnostics.HealthChecks":"(,10.0.32767]","Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions":"(,10.0.32767]","Microsoft.Extensions.Features":"(,10.0.32767]","Microsoft.Extensions.FileProviders.Abstractions":"(,10.0.32767]","Microsoft.Extensions.FileProviders.Composite":"(,10.0.32767]","Microsoft.Extensions.FileProviders.Physical":"(,10.0.32767]","Microsoft.Extensions.FileSystemGlobbing":"(,10.0.32767]","Microsoft.Extensions.Hosting":"(,10.0.32767]","Microsoft.Extensions.Hosting.Abstractions":"(,10.0.32767]","Microsoft.Extensions.Http":"(,10.0.32767]","Microsoft.Extensions.Identity.Core":"(,10.0.32767]","Microsoft.Extensions.Identity.Stores":"(,10.0.32767]","Microsoft.Extensions.Localization":"(,10.0.32767]","Microsoft.Extensions.Localization.Abstractions":"(,10.0.32767]","Microsoft.Extensions.Logging":"(,10.0.32767]","Microsoft.Extensions.Logging.Abstractions":"(,10.0.32767]","Microsoft.Extensions.Logging.Configuration":"(,10.0.32767]","Microsoft.Extensions.Logging.Console":"(,10.0.32767]","Microsoft.Extensions.Logging.Debug":"(,10.0.32767]","Microsoft.Extensions.Logging.EventLog":"(,10.0.32767]","Microsoft.Extensions.Logging.EventSource":"(,10.0.32767]","Microsoft.Extensions.Logging.TraceSource":"(,10.0.32767]","Microsoft.Extensions.ObjectPool":"(,10.0.32767]","Microsoft.Extensions.Options":"(,10.0.32767]","Microsoft.Extensions.Options.ConfigurationExtensions":"(,10.0.32767]","Microsoft.Extensions.Options.DataAnnotations":"(,10.0.32767]","Microsoft.Extensions.Primitives":"(,10.0.32767]","Microsoft.Extensions.Validation":"(,10.0.32767]","Microsoft.Extensions.WebEncoders":"(,10.0.32767]","Microsoft.JSInterop":"(,10.0.32767]","Microsoft.Net.Http.Headers":"(,10.0.32767]","Microsoft.VisualBasic":"(,10.4.32767]","Microsoft.Win32.Primitives":"(,4.3.32767]","Microsoft.Win32.Registry":"(,5.0.32767]","runtime.any.System.Collections":"(,4.3.32767]","runtime.any.System.Diagnostics.Tools":"(,4.3.32767]","runtime.any.System.Diagnostics.Tracing":"(,4.3.32767]","runtime.any.System.Globalization":"(,4.3.32767]","runtime.any.System.Globalization.Calendars":"(,4.3.32767]","runtime.any.System.IO":"(,4.3.32767]","runtime.any.System.Reflection":"(,4.3.32767]","runtime.any.System.Reflection.Extensions":"(,4.3.32767]","runtime.any.System.Reflection.Primitives":"(,4.3.32767]","runtime.any.System.Resources.ResourceManager":"(,4.3.32767]","runtime.any.System.Runtime":"(,4.3.32767]","runtime.any.System.Runtime.Handles":"(,4.3.32767]","runtime.any.System.Runtime.InteropServices":"(,4.3.32767]","runtime.any.System.Text.Encoding":"(,4.3.32767]","runtime.any.System.Text.Encoding.Extensions":"(,4.3.32767]","runtime.any.System.Threading.Tasks":"(,4.3.32767]","runtime.any.System.Threading.Timer":"(,4.3.32767]","runtime.aot.System.Collections":"(,4.3.32767]","runtime.aot.System.Diagnostics.Tools":"(,4.3.32767]","runtime.aot.System.Diagnostics.Tracing":"(,4.3.32767]","runtime.aot.System.Globalization":"(,4.3.32767]","runtime.aot.System.Globalization.Calendars":"(,4.3.32767]","runtime.aot.System.IO":"(,4.3.32767]","runtime.aot.System.Reflection":"(,4.3.32767]","runtime.aot.System.Reflection.Extensions":"(,4.3.32767]","runtime.aot.System.Reflection.Primitives":"(,4.3.32767]","runtime.aot.System.Resources.ResourceManager":"(,4.3.32767]","runtime.aot.System.Runtime":"(,4.3.32767]","runtime.aot.System.Runtime.Handles":"(,4.3.32767]","runtime.aot.System.Runtime.InteropServices":"(,4.3.32767]","runtime.aot.System.Text.Encoding":"(,4.3.32767]","runtime.aot.System.Text.Encoding.Extensions":"(,4.3.32767]","runtime.aot.System.Threading.Tasks":"(,4.3.32767]","runtime.aot.System.Threading.Timer":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.debian.9-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.27-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.fedora.28-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.opensuse.42.3-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple":"(,4.3.32767]","runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography":"(,4.3.32767]","runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http":"(,4.3.32767]","runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security":"(,4.3.32767]","runtime.unix.Microsoft.Win32.Primitives":"(,4.3.32767]","runtime.unix.System.Console":"(,4.3.32767]","runtime.unix.System.Diagnostics.Debug":"(,4.3.32767]","runtime.unix.System.IO.FileSystem":"(,4.3.32767]","runtime.unix.System.Net.Primitives":"(,4.3.32767]","runtime.unix.System.Net.Sockets":"(,4.3.32767]","runtime.unix.System.Private.Uri":"(,4.3.32767]","runtime.unix.System.Runtime.Extensions":"(,4.3.32767]","runtime.win.Microsoft.Win32.Primitives":"(,4.3.32767]","runtime.win.System.Console":"(,4.3.32767]","runtime.win.System.Diagnostics.Debug":"(,4.3.32767]","runtime.win.System.IO.FileSystem":"(,4.3.32767]","runtime.win.System.Net.Primitives":"(,4.3.32767]","runtime.win.System.Net.Sockets":"(,4.3.32767]","runtime.win.System.Runtime.Extensions":"(,4.3.32767]","runtime.win10-arm-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win10-arm64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win10-x64-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win10-x86-aot.runtime.native.System.IO.Compression":"(,4.0.32767]","runtime.win7-x64.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win7-x86.runtime.native.System.IO.Compression":"(,4.3.32767]","runtime.win7.System.Private.Uri":"(,4.3.32767]","runtime.win8-arm.runtime.native.System.IO.Compression":"(,4.3.32767]","System.AppContext":"(,4.3.32767]","System.Buffers":"(,5.0.32767]","System.Collections":"(,4.3.32767]","System.Collections.Concurrent":"(,4.3.32767]","System.Collections.Immutable":"(,10.0.32767]","System.Collections.NonGeneric":"(,4.3.32767]","System.Collections.Specialized":"(,4.3.32767]","System.ComponentModel":"(,4.3.32767]","System.ComponentModel.Annotations":"(,4.3.32767]","System.ComponentModel.EventBasedAsync":"(,4.3.32767]","System.ComponentModel.Primitives":"(,4.3.32767]","System.ComponentModel.TypeConverter":"(,4.3.32767]","System.Console":"(,4.3.32767]","System.Data.Common":"(,4.3.32767]","System.Data.DataSetExtensions":"(,4.4.32767]","System.Diagnostics.Contracts":"(,4.3.32767]","System.Diagnostics.Debug":"(,4.3.32767]","System.Diagnostics.DiagnosticSource":"(,10.0.32767]","System.Diagnostics.EventLog":"(,10.0.32767]","System.Diagnostics.FileVersionInfo":"(,4.3.32767]","System.Diagnostics.Process":"(,4.3.32767]","System.Diagnostics.StackTrace":"(,4.3.32767]","System.Diagnostics.TextWriterTraceListener":"(,4.3.32767]","System.Diagnostics.Tools":"(,4.3.32767]","System.Diagnostics.TraceSource":"(,4.3.32767]","System.Diagnostics.Tracing":"(,4.3.32767]","System.Drawing.Primitives":"(,4.3.32767]","System.Dynamic.Runtime":"(,4.3.32767]","System.Formats.Asn1":"(,10.0.32767]","System.Formats.Cbor":"(,10.0.32767]","System.Formats.Tar":"(,10.0.32767]","System.Globalization":"(,4.3.32767]","System.Globalization.Calendars":"(,4.3.32767]","System.Globalization.Extensions":"(,4.3.32767]","System.IO":"(,4.3.32767]","System.IO.Compression":"(,4.3.32767]","System.IO.Compression.ZipFile":"(,4.3.32767]","System.IO.FileSystem":"(,4.3.32767]","System.IO.FileSystem.AccessControl":"(,4.4.32767]","System.IO.FileSystem.DriveInfo":"(,4.3.32767]","System.IO.FileSystem.Primitives":"(,4.3.32767]","System.IO.FileSystem.Watcher":"(,4.3.32767]","System.IO.IsolatedStorage":"(,4.3.32767]","System.IO.MemoryMappedFiles":"(,4.3.32767]","System.IO.Pipelines":"(,10.0.32767]","System.IO.Pipes":"(,4.3.32767]","System.IO.Pipes.AccessControl":"(,5.0.32767]","System.IO.UnmanagedMemoryStream":"(,4.3.32767]","System.Linq":"(,4.3.32767]","System.Linq.AsyncEnumerable":"(,10.0.32767]","System.Linq.Expressions":"(,4.3.32767]","System.Linq.Parallel":"(,4.3.32767]","System.Linq.Queryable":"(,4.3.32767]","System.Memory":"(,5.0.32767]","System.Net.Http":"(,4.3.32767]","System.Net.Http.Json":"(,10.0.32767]","System.Net.NameResolution":"(,4.3.32767]","System.Net.NetworkInformation":"(,4.3.32767]","System.Net.Ping":"(,4.3.32767]","System.Net.Primitives":"(,4.3.32767]","System.Net.Requests":"(,4.3.32767]","System.Net.Security":"(,4.3.32767]","System.Net.ServerSentEvents":"(,10.0.32767]","System.Net.Sockets":"(,4.3.32767]","System.Net.WebHeaderCollection":"(,4.3.32767]","System.Net.WebSockets":"(,4.3.32767]","System.Net.WebSockets.Client":"(,4.3.32767]","System.Numerics.Vectors":"(,5.0.32767]","System.ObjectModel":"(,4.3.32767]","System.Private.DataContractSerialization":"(,4.3.32767]","System.Private.Uri":"(,4.3.32767]","System.Reflection":"(,4.3.32767]","System.Reflection.DispatchProxy":"(,6.0.32767]","System.Reflection.Emit":"(,4.7.32767]","System.Reflection.Emit.ILGeneration":"(,4.7.32767]","System.Reflection.Emit.Lightweight":"(,4.7.32767]","System.Reflection.Extensions":"(,4.3.32767]","System.Reflection.Metadata":"(,10.0.32767]","System.Reflection.Primitives":"(,4.3.32767]","System.Reflection.TypeExtensions":"(,4.3.32767]","System.Resources.Reader":"(,4.3.32767]","System.Resources.ResourceManager":"(,4.3.32767]","System.Resources.Writer":"(,4.3.32767]","System.Runtime":"(,4.3.32767]","System.Runtime.CompilerServices.Unsafe":"(,7.0.32767]","System.Runtime.CompilerServices.VisualC":"(,4.3.32767]","System.Runtime.Extensions":"(,4.3.32767]","System.Runtime.Handles":"(,4.3.32767]","System.Runtime.InteropServices":"(,4.3.32767]","System.Runtime.InteropServices.RuntimeInformation":"(,4.3.32767]","System.Runtime.Loader":"(,4.3.32767]","System.Runtime.Numerics":"(,4.3.32767]","System.Runtime.Serialization.Formatters":"(,4.3.32767]","System.Runtime.Serialization.Json":"(,4.3.32767]","System.Runtime.Serialization.Primitives":"(,4.3.32767]","System.Runtime.Serialization.Xml":"(,4.3.32767]","System.Security.AccessControl":"(,6.0.32767]","System.Security.Claims":"(,4.3.32767]","System.Security.Cryptography.Algorithms":"(,4.3.32767]","System.Security.Cryptography.Cng":"(,5.0.32767]","System.Security.Cryptography.Csp":"(,4.3.32767]","System.Security.Cryptography.Encoding":"(,4.3.32767]","System.Security.Cryptography.OpenSsl":"(,5.0.32767]","System.Security.Cryptography.Primitives":"(,4.3.32767]","System.Security.Cryptography.X509Certificates":"(,4.3.32767]","System.Security.Cryptography.Xml":"(,10.0.32767]","System.Security.Principal":"(,4.3.32767]","System.Security.Principal.Windows":"(,5.0.32767]","System.Security.SecureString":"(,4.3.32767]","System.Text.Encoding":"(,4.3.32767]","System.Text.Encoding.CodePages":"(,10.0.32767]","System.Text.Encoding.Extensions":"(,4.3.32767]","System.Text.Encodings.Web":"(,10.0.32767]","System.Text.Json":"(,10.0.32767]","System.Text.RegularExpressions":"(,4.3.32767]","System.Threading":"(,4.3.32767]","System.Threading.AccessControl":"(,10.0.32767]","System.Threading.Channels":"(,10.0.32767]","System.Threading.Overlapped":"(,4.3.32767]","System.Threading.RateLimiting":"(,10.0.32767]","System.Threading.Tasks":"(,4.3.32767]","System.Threading.Tasks.Dataflow":"(,10.0.32767]","System.Threading.Tasks.Extensions":"(,5.0.32767]","System.Threading.Tasks.Parallel":"(,4.3.32767]","System.Threading.Thread":"(,4.3.32767]","System.Threading.ThreadPool":"(,4.3.32767]","System.Threading.Timer":"(,4.3.32767]","System.ValueTuple":"(,4.5.32767]","System.Xml.ReaderWriter":"(,4.3.32767]","System.Xml.XDocument":"(,4.3.32767]","System.Xml.XmlDocument":"(,4.3.32767]","System.Xml.XmlSerializer":"(,4.3.32767]","System.Xml.XPath":"(,4.3.32767]","System.Xml.XPath.XDocument":"(,5.0.32767]"}}} \ No newline at end of file diff --git a/MediaBrowser.Common/obj/rider.project.model.nuget.info b/MediaBrowser.Common/obj/rider.project.model.nuget.info new file mode 100644 index 00000000..23e57a0d --- /dev/null +++ b/MediaBrowser.Common/obj/rider.project.model.nuget.info @@ -0,0 +1 @@ +17714532047800000 \ No newline at end of file diff --git a/MediaBrowser.Common/obj/rider.project.restore.info b/MediaBrowser.Common/obj/rider.project.restore.info new file mode 100644 index 00000000..a694cf2f --- /dev/null +++ b/MediaBrowser.Common/obj/rider.project.restore.info @@ -0,0 +1 @@ +17715044198300000 \ No newline at end of file diff --git a/MediaBrowser.Controller/Authentication/AuthenticationException.cs b/MediaBrowser.Controller/Authentication/AuthenticationException.cs new file mode 100644 index 00000000..081f877f --- /dev/null +++ b/MediaBrowser.Controller/Authentication/AuthenticationException.cs @@ -0,0 +1,35 @@ +using System; + +namespace MediaBrowser.Controller.Authentication +{ + /// + /// The exception that is thrown when an attempt to authenticate fails. + /// + public class AuthenticationException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public AuthenticationException() : base() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public AuthenticationException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. + public AuthenticationException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs new file mode 100644 index 00000000..daf4d963 --- /dev/null +++ b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs @@ -0,0 +1,31 @@ +#nullable disable + +using MediaBrowser.Model.Dto; + +namespace MediaBrowser.Controller.Authentication; + +/// +/// A class representing an authentication result. +/// +public class AuthenticationResult +{ + /// + /// Gets or sets the user. + /// + public UserDto User { get; set; } + + /// + /// Gets or sets the session info. + /// + public SessionInfoDto SessionInfo { get; set; } + + /// + /// Gets or sets the access token. + /// + public string AccessToken { get; set; } + + /// + /// Gets or sets the server id. + /// + public string ServerId { get; set; } +} diff --git a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs new file mode 100644 index 00000000..c993ceea --- /dev/null +++ b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs @@ -0,0 +1,36 @@ +#pragma warning disable CS1591 + +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Model.Users; + +namespace MediaBrowser.Controller.Authentication +{ + public interface IAuthenticationProvider + { + string Name { get; } + + bool IsEnabled { get; } + + Task Authenticate(string username, string password); + + Task ChangePassword(User user, string newPassword); + } + + public interface IRequiresResolvedUser + { + Task Authenticate(string username, string password, User? resolvedUser); + } + + public interface IHasNewUserPolicy + { + UserPolicy GetNewUserPolicy(); + } + + public class ProviderAuthenticationResult + { + public required string Username { get; set; } + + public string? DisplayName { get; set; } + } +} diff --git a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs new file mode 100644 index 00000000..36cd5c5d --- /dev/null +++ b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs @@ -0,0 +1,28 @@ +#pragma warning disable CS1591 + +using System; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Model.Users; + +namespace MediaBrowser.Controller.Authentication +{ + public interface IPasswordResetProvider + { + string Name { get; } + + bool IsEnabled { get; } + + Task StartForgotPasswordProcess(User? user, string enteredUsername, bool isInNetwork); + + Task RedeemPasswordResetPin(string pin); + } + +#nullable disable + public class PasswordPinCreationResult + { + public string PinFile { get; set; } + + public DateTime ExpirationDate { get; set; } + } +} diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs new file mode 100644 index 00000000..6acab13f --- /dev/null +++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs @@ -0,0 +1,73 @@ +using System; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Configuration; + +namespace MediaBrowser.Controller.BaseItemManager +{ + /// + public class BaseItemManager : IBaseItemManager + { + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + public BaseItemManager(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// + public bool IsMetadataFetcherEnabled(BaseItem baseItem, TypeOptions? libraryTypeOptions, string name) + { + if (baseItem is Channel) + { + // Hack alert. + return true; + } + + if (baseItem.SourceType == SourceType.Channel) + { + // Hack alert. + return !baseItem.EnableMediaSourceDisplay; + } + + if (libraryTypeOptions is not null) + { + return libraryTypeOptions.MetadataFetchers.Contains(name, StringComparison.OrdinalIgnoreCase); + } + + var itemConfig = _serverConfigurationManager.GetMetadataOptionsForType(baseItem.GetType().Name); + return itemConfig is null || !itemConfig.DisabledMetadataFetchers.Contains(name, StringComparison.OrdinalIgnoreCase); + } + + /// + public bool IsImageFetcherEnabled(BaseItem baseItem, TypeOptions? libraryTypeOptions, string name) + { + if (baseItem is Channel) + { + // Hack alert. + return true; + } + + if (baseItem.SourceType == SourceType.Channel) + { + // Hack alert. + return !baseItem.EnableMediaSourceDisplay; + } + + if (libraryTypeOptions is not null) + { + return libraryTypeOptions.ImageFetchers.Contains(name, StringComparison.OrdinalIgnoreCase); + } + + var itemConfig = _serverConfigurationManager.GetMetadataOptionsForType(baseItem.GetType().Name); + return itemConfig is null || !itemConfig.DisabledImageFetchers.Contains(name, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs new file mode 100644 index 00000000..975218ad --- /dev/null +++ b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs @@ -0,0 +1,29 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Configuration; + +namespace MediaBrowser.Controller.BaseItemManager +{ + /// + /// The BaseItem manager. + /// + public interface IBaseItemManager + { + /// + /// Is metadata fetcher enabled. + /// + /// The base item. + /// The type options for baseItem from the library (if defined). + /// The metadata fetcher name. + /// true if metadata fetcher is enabled, else false. + bool IsMetadataFetcherEnabled(BaseItem baseItem, TypeOptions? libraryTypeOptions, string name); + + /// + /// Is image fetcher enabled. + /// + /// The base item. + /// The type options for baseItem from the library (if defined). + /// The image fetcher name. + /// true if image fetcher is enabled, else false. + bool IsImageFetcherEnabled(BaseItem baseItem, TypeOptions? libraryTypeOptions, string name); + } +} diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs new file mode 100644 index 00000000..199e22b3 --- /dev/null +++ b/MediaBrowser.Controller/Channels/Channel.cs @@ -0,0 +1,87 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using System.Globalization; +using System.Linq; +using System.Text.Json.Serialization; +using System.Threading; +using Jellyfin.Data; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Querying; + +namespace MediaBrowser.Controller.Channels +{ + public class Channel : Folder + { + [JsonIgnore] + public override bool SupportsInheritedParentImages => false; + + [JsonIgnore] + public override SourceType SourceType => SourceType.Channel; + + public override bool IsVisible(User user, bool skipAllowedTagsCheck = false) + { + var blockedChannelsPreference = user.GetPreferenceValues(PreferenceKind.BlockedChannels); + if (blockedChannelsPreference.Length != 0) + { + if (blockedChannelsPreference.Contains(Id)) + { + return false; + } + } + else + { + if (!user.HasPermission(PermissionKind.EnableAllChannels) + && !user.GetPreferenceValues(PreferenceKind.EnabledChannels).Contains(Id)) + { + return false; + } + } + + return base.IsVisible(user, skipAllowedTagsCheck); + } + + protected override QueryResult GetItemsInternal(InternalItemsQuery query) + { + try + { + query.Parent = this; + query.ChannelIds = new Guid[] { Id }; + + // Don't blow up here because it could cause parent screens with other content to fail + return ChannelManager.GetChannelItemsInternal(query, new Progress(), CancellationToken.None).GetAwaiter().GetResult(); + } + catch + { + // Already logged at lower levels + return new QueryResult(); + } + } + + protected override string GetInternalMetadataPath(string basePath) + { + return GetInternalMetadataPath(basePath, Id); + } + + public static string GetInternalMetadataPath(string basePath, Guid id) + { + return System.IO.Path.Combine(basePath, "channels", id.ToString("N", CultureInfo.InvariantCulture), "metadata"); + } + + public override bool CanDelete() + { + return false; + } + + internal static bool IsChannelVisible(BaseItem channelItem, User user) + { + var channel = ChannelManager.GetChannel(channelItem.ChannelId.ToString(string.Empty)); + + return channel.IsVisible(user); + } + } +} diff --git a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs new file mode 100644 index 00000000..55f80b24 --- /dev/null +++ b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs @@ -0,0 +1,97 @@ +#nullable disable + +#pragma warning disable CA1002, CA2227, CS1591 + +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Channels; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Controller.Channels +{ + public class ChannelItemInfo : IHasProviderIds + { + public ChannelItemInfo() + { + MediaSources = new List(); + TrailerTypes = new List(); + Genres = new List(); + Studios = new List(); + People = new List(); + Tags = new List(); + ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + Artists = new List(); + AlbumArtists = new List(); + } + + public string Name { get; set; } + + public string SeriesName { get; set; } + + public string Id { get; set; } + + public DateTime DateModified { get; set; } + + public ChannelItemType Type { get; set; } + + public string OfficialRating { get; set; } + + public string Overview { get; set; } + + public List Genres { get; set; } + + public List Studios { get; set; } + + public List Tags { get; set; } + + public List People { get; set; } + + public float? CommunityRating { get; set; } + + public long? RunTimeTicks { get; set; } + + public string ImageUrl { get; set; } + + public string OriginalTitle { get; set; } + + public ChannelMediaType MediaType { get; set; } + + public ChannelFolderType FolderType { get; set; } + + public ChannelMediaContentType ContentType { get; set; } + + public ExtraType ExtraType { get; set; } + + public List TrailerTypes { get; set; } + + public Dictionary ProviderIds { get; set; } + + public DateTime? PremiereDate { get; set; } + + public int? ProductionYear { get; set; } + + public DateTime? DateCreated { get; set; } + + public DateTime? StartDate { get; set; } + + public DateTime? EndDate { get; set; } + + public int? IndexNumber { get; set; } + + public int? ParentIndexNumber { get; set; } + + public List MediaSources { get; set; } + + public string HomePageUrl { get; set; } + + public List Artists { get; set; } + + public List AlbumArtists { get; set; } + + public bool IsLiveStream { get; set; } + + public string Etag { get; set; } + } +} diff --git a/MediaBrowser.Controller/Channels/ChannelItemResult.cs b/MediaBrowser.Controller/Channels/ChannelItemResult.cs new file mode 100644 index 00000000..ca772199 --- /dev/null +++ b/MediaBrowser.Controller/Channels/ChannelItemResult.cs @@ -0,0 +1,19 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Channels +{ + public class ChannelItemResult + { + public ChannelItemResult() + { + Items = Array.Empty(); + } + + public IReadOnlyList Items { get; set; } + + public int? TotalRecordCount { get; set; } + } +} diff --git a/MediaBrowser.Controller/Channels/ChannelItemType.cs b/MediaBrowser.Controller/Channels/ChannelItemType.cs new file mode 100644 index 00000000..3ce920e2 --- /dev/null +++ b/MediaBrowser.Controller/Channels/ChannelItemType.cs @@ -0,0 +1,11 @@ +#pragma warning disable CS1591 + +namespace MediaBrowser.Controller.Channels +{ + public enum ChannelItemType + { + Media = 0, + + Folder = 1 + } +} diff --git a/MediaBrowser.Controller/Channels/ChannelLatestMediaSearch.cs b/MediaBrowser.Controller/Channels/ChannelLatestMediaSearch.cs new file mode 100644 index 00000000..ebbe1376 --- /dev/null +++ b/MediaBrowser.Controller/Channels/ChannelLatestMediaSearch.cs @@ -0,0 +1,11 @@ +#nullable disable + +#pragma warning disable CS1591 + +namespace MediaBrowser.Controller.Channels +{ + public class ChannelLatestMediaSearch + { + public string UserId { get; set; } + } +} diff --git a/MediaBrowser.Controller/Channels/ChannelParentalRating.cs b/MediaBrowser.Controller/Channels/ChannelParentalRating.cs new file mode 100644 index 00000000..f77d81c1 --- /dev/null +++ b/MediaBrowser.Controller/Channels/ChannelParentalRating.cs @@ -0,0 +1,17 @@ +#pragma warning disable CS1591 + +namespace MediaBrowser.Controller.Channels +{ + public enum ChannelParentalRating + { + GeneralAudience = 0, + + UsPG = 1, + + UsPG13 = 2, + + UsR = 3, + + Adult = 4 + } +} diff --git a/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs b/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs new file mode 100644 index 00000000..990b025b --- /dev/null +++ b/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs @@ -0,0 +1,13 @@ +#nullable disable + +#pragma warning disable CS1591 + +namespace MediaBrowser.Controller.Channels +{ + public class ChannelSearchInfo + { + public string SearchTerm { get; set; } + + public string UserId { get; set; } + } +} diff --git a/MediaBrowser.Controller/Channels/IChannel.cs b/MediaBrowser.Controller/Channels/IChannel.cs new file mode 100644 index 00000000..01bf8d5c --- /dev/null +++ b/MediaBrowser.Controller/Channels/IChannel.cs @@ -0,0 +1,80 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Controller.Channels +{ + public interface IChannel + { + /// + /// Gets the name. + /// + /// The name. + string Name { get; } + + /// + /// Gets the description. + /// + /// The description. + string Description { get; } + + /// + /// Gets the data version. + /// + /// The data version. + string DataVersion { get; } + + /// + /// Gets the home page URL. + /// + /// The home page URL. + string HomePageUrl { get; } + + /// + /// Gets the parental rating. + /// + /// The parental rating. + ChannelParentalRating ParentalRating { get; } + + /// + /// Gets the channel information. + /// + /// ChannelFeatures. + InternalChannelFeatures GetChannelFeatures(); + + /// + /// Determines whether [is enabled for] [the specified user]. + /// + /// The user identifier. + /// true if [is enabled for] [the specified user]; otherwise, false. + bool IsEnabledFor(string userId); + + /// + /// Gets the channel items. + /// + /// The query. + /// The cancellation token. + /// Task{IEnumerable{ChannelItem}}. + Task GetChannelItems(InternalChannelItemQuery query, CancellationToken cancellationToken); + + /// + /// Gets the channel image. + /// + /// The type. + /// The cancellation token. + /// Task{DynamicImageResponse}. + Task GetChannelImage(ImageType type, CancellationToken cancellationToken); + + /// + /// Gets the supported channel images. + /// + /// IEnumerable{ImageType}. + IEnumerable GetSupportedChannelImages(); + } +} diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs new file mode 100644 index 00000000..c8b432ec --- /dev/null +++ b/MediaBrowser.Controller/Channels/IChannelManager.cs @@ -0,0 +1,99 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Channels; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Querying; + +namespace MediaBrowser.Controller.Channels +{ + public interface IChannelManager + { + /// + /// Gets the channel features. + /// + /// The identifier. + /// ChannelFeatures. + ChannelFeatures GetChannelFeatures(Guid? id); + + /// + /// Gets all channel features. + /// + /// IEnumerable{ChannelFeatures}. + ChannelFeatures[] GetAllChannelFeatures(); + + bool EnableMediaSourceDisplay(BaseItem item); + + bool CanDelete(BaseItem item); + + Task DeleteItem(BaseItem item); + + /// + /// Gets the channel. + /// + /// The identifier. + /// Channel. + Channel GetChannel(string id); + + /// + /// Gets the channels internal. + /// + /// The query. + /// The channels. + Task> GetChannelsInternalAsync(ChannelQuery query); + + /// + /// Gets the channels. + /// + /// The query. + /// The channels. + Task> GetChannelsAsync(ChannelQuery query); + + /// + /// Gets the latest channel items. + /// + /// The item query. + /// The cancellation token. + /// The latest channels. + Task> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken); + + /// + /// Gets the latest channel items. + /// + /// The item query. + /// The cancellation token. + /// The latest channels. + Task> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken); + + /// + /// Gets the channel items. + /// + /// The query. + /// The cancellation token. + /// The channel items. + Task> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken); + + /// + /// Gets the channel items. + /// + /// The query. + /// The progress to report to. + /// The cancellation token. + /// The channel items. + Task> GetChannelItemsInternal(InternalItemsQuery query, IProgress progress, CancellationToken cancellationToken); + + /// + /// Gets the channel item media sources. + /// + /// The item. + /// The cancellation token. + /// The item media sources. + IEnumerable GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken); + } +} diff --git a/MediaBrowser.Controller/Channels/IDisableMediaSourceDisplay.cs b/MediaBrowser.Controller/Channels/IDisableMediaSourceDisplay.cs new file mode 100644 index 00000000..51a5ca8b --- /dev/null +++ b/MediaBrowser.Controller/Channels/IDisableMediaSourceDisplay.cs @@ -0,0 +1,12 @@ +namespace MediaBrowser.Controller.Channels +{ + /// + /// Disable media source display. + /// + /// + /// can inherit this interface to disable being displayed. + /// + public interface IDisableMediaSourceDisplay + { + } +} diff --git a/MediaBrowser.Controller/Channels/IHasCacheKey.cs b/MediaBrowser.Controller/Channels/IHasCacheKey.cs new file mode 100644 index 00000000..7d5207c3 --- /dev/null +++ b/MediaBrowser.Controller/Channels/IHasCacheKey.cs @@ -0,0 +1,14 @@ +#pragma warning disable CS1591 + +namespace MediaBrowser.Controller.Channels +{ + public interface IHasCacheKey + { + /// + /// Gets the cache key. + /// + /// The user identifier. + /// System.String. + string? GetCacheKey(string? userId); + } +} diff --git a/MediaBrowser.Controller/Channels/IHasFolderAttributes.cs b/MediaBrowser.Controller/Channels/IHasFolderAttributes.cs new file mode 100644 index 00000000..39fac78d --- /dev/null +++ b/MediaBrowser.Controller/Channels/IHasFolderAttributes.cs @@ -0,0 +1,9 @@ +#pragma warning disable CA1819, CS1591 + +namespace MediaBrowser.Controller.Channels +{ + public interface IHasFolderAttributes + { + string[] Attributes { get; } + } +} diff --git a/MediaBrowser.Controller/Channels/IRequiresMediaInfoCallback.cs b/MediaBrowser.Controller/Channels/IRequiresMediaInfoCallback.cs new file mode 100644 index 00000000..eeaa6b62 --- /dev/null +++ b/MediaBrowser.Controller/Channels/IRequiresMediaInfoCallback.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Dto; + +namespace MediaBrowser.Controller.Channels +{ + /// + /// The channel requires a media info callback. + /// + public interface IRequiresMediaInfoCallback + { + /// + /// Gets the channel item media information. + /// + /// The channel item id. + /// The cancellation token. + /// The enumerable of media source info. + Task> GetChannelItemMediaInfo(string id, CancellationToken cancellationToken); + } +} diff --git a/MediaBrowser.Controller/Channels/ISupportsDelete.cs b/MediaBrowser.Controller/Channels/ISupportsDelete.cs new file mode 100644 index 00000000..0110bfa7 --- /dev/null +++ b/MediaBrowser.Controller/Channels/ISupportsDelete.cs @@ -0,0 +1,15 @@ +#pragma warning disable CS1591 + +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; + +namespace MediaBrowser.Controller.Channels +{ + public interface ISupportsDelete + { + bool CanDelete(BaseItem item); + + Task DeleteItem(string id, CancellationToken cancellationToken); + } +} diff --git a/MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs b/MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs new file mode 100644 index 00000000..1935ec0f --- /dev/null +++ b/MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs @@ -0,0 +1,19 @@ +#pragma warning disable CS1591 + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Channels +{ + public interface ISupportsLatestMedia + { + /// + /// Gets the latest media. + /// + /// The request. + /// The cancellation token. + /// The latest media. + Task> GetLatestMedia(ChannelLatestMediaSearch request, CancellationToken cancellationToken); + } +} diff --git a/MediaBrowser.Controller/Channels/ISupportsMediaProbe.cs b/MediaBrowser.Controller/Channels/ISupportsMediaProbe.cs new file mode 100644 index 00000000..dbd256a6 --- /dev/null +++ b/MediaBrowser.Controller/Channels/ISupportsMediaProbe.cs @@ -0,0 +1,9 @@ +namespace MediaBrowser.Controller.Channels +{ + /// + /// Channel supports media probe. + /// + public interface ISupportsMediaProbe + { + } +} diff --git a/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs b/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs new file mode 100644 index 00000000..39499686 --- /dev/null +++ b/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs @@ -0,0 +1,66 @@ +#nullable disable + +#pragma warning disable CA1002, CA2227, CS1591 + +using System.Collections.Generic; +using MediaBrowser.Model.Channels; + +namespace MediaBrowser.Controller.Channels +{ + public class InternalChannelFeatures + { + public InternalChannelFeatures() + { + MediaTypes = new List(); + ContentTypes = new List(); + + DefaultSortFields = new List(); + } + + /// + /// Gets or sets the media types. + /// + /// The media types. + public List MediaTypes { get; set; } + + /// + /// Gets or sets the content types. + /// + /// The content types. + public List ContentTypes { get; set; } + + /// + /// Gets or sets the maximum number of records the channel allows retrieving at a time. + /// + public int? MaxPageSize { get; set; } + + /// + /// Gets or sets the default sort orders. + /// + /// The default sort orders. + public List DefaultSortFields { get; set; } + + /// + /// Gets or sets a value indicating whether a sort ascending/descending toggle is supported or not. + /// + public bool SupportsSortOrderToggle { get; set; } + + /// + /// Gets or sets the automatic refresh levels. + /// + /// The automatic refresh levels. + public int? AutoRefreshLevels { get; set; } + + /// + /// Gets or sets the daily download limit. + /// + /// The daily download limit. + public int? DailyDownloadLimit { get; set; } + + /// + /// Gets or sets a value indicating whether [supports downloading]. + /// + /// true if [supports downloading]; otherwise, false. + public bool SupportsContentDownloading { get; set; } + } +} diff --git a/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs b/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs new file mode 100644 index 00000000..0d837fac --- /dev/null +++ b/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs @@ -0,0 +1,24 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using MediaBrowser.Model.Channels; + +namespace MediaBrowser.Controller.Channels +{ + public class InternalChannelItemQuery + { + public string FolderId { get; set; } + + public Guid UserId { get; set; } + + public int? StartIndex { get; set; } + + public int? Limit { get; set; } + + public ChannelItemSortField? SortBy { get; set; } + + public bool SortDescending { get; set; } + } +} diff --git a/MediaBrowser.Controller/Chapters/IChapterManager.cs b/MediaBrowser.Controller/Chapters/IChapterManager.cs new file mode 100644 index 00000000..25656fd6 --- /dev/null +++ b/MediaBrowser.Controller/Chapters/IChapterManager.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Controller.Chapters; + +/// +/// Interface IChapterManager. +/// +public interface IChapterManager +{ + /// + /// Saves the chapters. + /// + /// The video. + /// The set of chapters. + void SaveChapters(Video video, IReadOnlyList chapters); + + /// + /// Gets a single chapter of a BaseItem on a specific index. + /// + /// The BaseItems id. + /// The index of that chapter. + /// A chapter instance. + ChapterInfo? GetChapter(Guid baseItemId, int index); + + /// + /// Gets all chapters associated with the baseItem. + /// + /// The BaseItems id. + /// A readonly list of chapter instances. + IReadOnlyList GetChapters(Guid baseItemId); + + /// + /// Refreshes the chapter images. + /// + /// Video to use. + /// Directory service to use. + /// Set of chapters to refresh. + /// Option to extract images. + /// Option to save chapters. + /// CancellationToken to use for operation. + /// true if successful, false if not. + Task RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken); + + /// + /// Deletes the chapter data. + /// + /// The item id. + /// The cancellation token. + /// Task. + Task DeleteChapterDataAsync(Guid itemId, CancellationToken cancellationToken); +} diff --git a/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs b/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs new file mode 100644 index 00000000..14dc64da --- /dev/null +++ b/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs @@ -0,0 +1,34 @@ +using System; +using System.IO; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.ClientEvent +{ + /// + public class ClientEventLogger : IClientEventLogger + { + private readonly IServerApplicationPaths _applicationPaths; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + public ClientEventLogger(IServerApplicationPaths applicationPaths) + { + _applicationPaths = applicationPaths; + } + + /// + public async Task WriteDocumentAsync(string clientName, string clientVersion, Stream fileContents) + { + var fileName = $"upload_{clientName}_{clientVersion}_{DateTime.UtcNow:yyyyMMddHHmmss}_{Guid.NewGuid():N}.log"; + var logFilePath = Path.Combine(_applicationPaths.LogDirectoryPath, fileName); + var fileStream = new FileStream(logFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.None); + await using (fileStream.ConfigureAwait(false)) + { + await fileContents.CopyToAsync(fileStream).ConfigureAwait(false); + return fileName; + } + } + } +} diff --git a/MediaBrowser.Controller/ClientEvent/IClientEventLogger.cs b/MediaBrowser.Controller/ClientEvent/IClientEventLogger.cs new file mode 100644 index 00000000..efb25457 --- /dev/null +++ b/MediaBrowser.Controller/ClientEvent/IClientEventLogger.cs @@ -0,0 +1,23 @@ +using System.IO; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.ClientEvent +{ + /// + /// The client event logger. + /// + public interface IClientEventLogger + { + /// + /// Writes a file to the log directory. + /// + /// The client name writing the document. + /// The client version writing the document. + /// The file contents to write. + /// The created file name. + Task WriteDocumentAsync( + string clientName, + string clientVersion, + Stream fileContents); + } +} diff --git a/MediaBrowser.Controller/Collections/CollectionCreatedEventArgs.cs b/MediaBrowser.Controller/Collections/CollectionCreatedEventArgs.cs new file mode 100644 index 00000000..456290f1 --- /dev/null +++ b/MediaBrowser.Controller/Collections/CollectionCreatedEventArgs.cs @@ -0,0 +1,24 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using MediaBrowser.Controller.Entities.Movies; + +namespace MediaBrowser.Controller.Collections +{ + public class CollectionCreatedEventArgs : EventArgs + { + /// + /// Gets or sets the collection. + /// + /// The collection. + public BoxSet Collection { get; set; } + + /// + /// Gets or sets the options. + /// + /// The options. + public CollectionCreationOptions Options { get; set; } + } +} diff --git a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs new file mode 100644 index 00000000..30f5f4ef --- /dev/null +++ b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs @@ -0,0 +1,32 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Controller.Collections +{ + public class CollectionCreationOptions : IHasProviderIds + { + public CollectionCreationOptions() + { + ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + ItemIdList = Array.Empty(); + UserIds = Array.Empty(); + } + + public string Name { get; set; } + + public Guid? ParentId { get; set; } + + public bool IsLocked { get; set; } + + public Dictionary ProviderIds { get; set; } + + public IReadOnlyList ItemIdList { get; set; } + + public IReadOnlyList UserIds { get; set; } + } +} diff --git a/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs b/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs new file mode 100644 index 00000000..e538fa4b --- /dev/null +++ b/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs @@ -0,0 +1,30 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; + +namespace MediaBrowser.Controller.Collections +{ + public class CollectionModifiedEventArgs : EventArgs + { + public CollectionModifiedEventArgs(BoxSet collection, IReadOnlyCollection itemsChanged) + { + Collection = collection; + ItemsChanged = itemsChanged; + } + + /// + /// Gets or sets the collection. + /// + /// The collection. + public BoxSet Collection { get; set; } + + /// + /// Gets or sets the items changed. + /// + /// The items changed. + public IReadOnlyCollection ItemsChanged { get; set; } + } +} diff --git a/MediaBrowser.Controller/Collections/ICollectionManager.cs b/MediaBrowser.Controller/Collections/ICollectionManager.cs new file mode 100644 index 00000000..206b5ac4 --- /dev/null +++ b/MediaBrowser.Controller/Collections/ICollectionManager.cs @@ -0,0 +1,67 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; + +namespace MediaBrowser.Controller.Collections +{ + public interface ICollectionManager + { + /// + /// Occurs when [collection created]. + /// + event EventHandler? CollectionCreated; + + /// + /// Occurs when [items added to collection]. + /// + event EventHandler? ItemsAddedToCollection; + + /// + /// Occurs when [items removed from collection]. + /// + event EventHandler? ItemsRemovedFromCollection; + + /// + /// Creates the collection. + /// + /// The options. + /// BoxSet wrapped in an awaitable task. + Task CreateCollectionAsync(CollectionCreationOptions options); + + /// + /// Adds to collection. + /// + /// The collection identifier. + /// The item ids. + /// representing the asynchronous operation. + Task AddToCollectionAsync(Guid collectionId, IEnumerable itemIds); + + /// + /// Removes from collection. + /// + /// The collection identifier. + /// The item ids. + /// A representing the asynchronous operation. + Task RemoveFromCollectionAsync(Guid collectionId, IEnumerable itemIds); + + /// + /// Collapses the items within box sets. + /// + /// The items. + /// The user. + /// IEnumerable{BaseItem}. + IEnumerable CollapseItemsWithinBoxSets(IEnumerable items, User user); + + /// + /// Gets the folder where collections are stored. + /// + /// Will create the collection folder on the storage if set to true. + /// The folder instance referencing the collection storage. + Task GetCollectionsFolder(bool createIfNeeded); + } +} diff --git a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs new file mode 100644 index 00000000..43ad04db --- /dev/null +++ b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs @@ -0,0 +1,23 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Configuration; + +namespace MediaBrowser.Controller.Configuration +{ + /// + /// Interface IServerConfigurationManager. + /// + public interface IServerConfigurationManager : IConfigurationManager + { + /// + /// Gets the application paths. + /// + /// The application paths. + IServerApplicationPaths ApplicationPaths { get; } + + /// + /// Gets the configuration. + /// + /// The configuration. + ServerConfiguration Configuration { get; } + } +} diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs new file mode 100644 index 00000000..ea38950d --- /dev/null +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -0,0 +1,117 @@ +using System; +using System.Threading.Tasks; +using Jellyfin.Data.Dtos; +using Jellyfin.Data.Events; +using Jellyfin.Data.Queries; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Entities.Security; +using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Querying; +using MediaBrowser.Model.Session; + +namespace MediaBrowser.Controller.Devices; + +/// +/// Device manager interface. +/// +public interface IDeviceManager +{ + /// + /// Event handler for updated device options. + /// + event EventHandler>> DeviceOptionsUpdated; + + /// + /// Creates a new device. + /// + /// The device to create. + /// A representing the creation of the device. + Task CreateDevice(Device device); + + /// + /// Saves the capabilities. + /// + /// The device id. + /// The capabilities. + void SaveCapabilities(string deviceId, ClientCapabilities capabilities); + + /// + /// Gets the capabilities. + /// + /// The device id. + /// ClientCapabilities. + ClientCapabilities GetCapabilities(string? deviceId); + + /// + /// Gets the device information. + /// + /// The identifier. + /// DeviceInfoDto. + DeviceInfoDto? GetDevice(string id); + + /// + /// Gets devices based on the provided query. + /// + /// The device query. + /// A representing the retrieval of the devices. + QueryResult GetDevices(DeviceQuery query); + + /// + /// Gets device information based on the provided query. + /// + /// The device query. + /// A representing the retrieval of the device information. + QueryResult GetDeviceInfos(DeviceQuery query); + + /// + /// Gets the device information. + /// + /// The user's id, or null. + /// IEnumerable<DeviceInfoDto>. + QueryResult GetDevicesForUser(Guid? userId); + + /// + /// Deletes a device. + /// + /// The device. + /// A representing the deletion of the device. + Task DeleteDevice(Device device); + + /// + /// Updates a device. + /// + /// The device. + /// A representing the update of the device. + Task UpdateDevice(Device device); + + /// + /// Determines whether this instance [can access device] the specified user identifier. + /// + /// The user to test. + /// The device id to test. + /// Whether the user can access the device. + bool CanAccessDevice(User user, string deviceId); + + /// + /// Updates the options of a device. + /// + /// The device id. + /// The device name. + /// A representing the update of the device options. + Task UpdateDeviceOptions(string deviceId, string? deviceName); + + /// + /// Gets the options of a device. + /// + /// The device id. + /// of the device. + DeviceOptionsDto? GetDeviceOptions(string deviceId); + + /// + /// Gets the dto for client capabilities. + /// + /// The client capabilities. + /// of the device. + ClientCapabilitiesDto ToClientCapabilitiesDto(ClientCapabilities capabilities); +} diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs new file mode 100644 index 00000000..c7bfbdb5 --- /dev/null +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -0,0 +1,95 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using MediaBrowser.Model.Drawing; + +namespace MediaBrowser.Controller.Drawing +{ + public interface IImageEncoder + { + /// + /// Gets the supported input formats. + /// + /// The supported input formats. + IReadOnlyCollection SupportedInputFormats { get; } + + /// + /// Gets the supported output formats. + /// + /// The supported output formats. + IReadOnlyCollection SupportedOutputFormats { get; } + + /// + /// Gets the display name for the encoder. + /// + /// The display name. + string Name { get; } + + /// + /// Gets a value indicating whether [supports image collage creation]. + /// + /// true if [supports image collage creation]; otherwise, false. + bool SupportsImageCollageCreation { get; } + + /// + /// Gets a value indicating whether [supports image encoding]. + /// + /// true if [supports image encoding]; otherwise, false. + bool SupportsImageEncoding { get; } + + /// + /// Get the dimensions of an image from the filesystem. + /// + /// The filepath of the image. + /// The image dimensions. + ImageDimensions GetImageSize(string path); + + /// + /// Gets the blurhash of an image. + /// + /// Amount of X components of DCT to take. + /// Amount of Y components of DCT to take. + /// The filepath of the image. + /// The blurhash. + string GetImageBlurHash(int xComp, int yComp, string path); + + /// + /// Encode an image. + /// + /// Input path of image. + /// Date modified. + /// Output path of image. + /// Auto-orient image. + /// Desired orientation of image. + /// Quality of encoded image. + /// Image processing options. + /// Image format of output. + /// Path of encoded image. + string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat); + + /// + /// Create an image collage. + /// + /// The options to use when creating the collage. + /// Optional. + void CreateImageCollage(ImageCollageOptions options, string? libraryName); + + /// + /// Creates a new splashscreen image. + /// + /// The list of poster paths. + /// The list of backdrop paths. + void CreateSplashscreen(IReadOnlyList posters, IReadOnlyList backdrops); + + /// + /// Creates a new trickplay tile image. + /// + /// The options to use when creating the image. Width and Height are a quantity of thumbnails in this case, not pixels. + /// The image encode quality. + /// The width of a single trickplay thumbnail. + /// Optional height of a single trickplay thumbnail, if it is known. + /// Height of single decoded trickplay thumbnail. + int CreateTrickplayTile(ImageCollageOptions options, int quality, int imgWidth, int? imgHeight); + } +} diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs new file mode 100644 index 00000000..4eeec99b --- /dev/null +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -0,0 +1,117 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Controller.Drawing +{ + /// + /// Interface IImageProcessor. + /// + public interface IImageProcessor + { + /// + /// Gets the supported input formats. + /// + /// The supported input formats. + IReadOnlyCollection SupportedInputFormats { get; } + + /// + /// Gets a value indicating whether [supports image collage creation]. + /// + /// true if [supports image collage creation]; otherwise, false. + bool SupportsImageCollageCreation { get; } + + /// + /// Gets the dimensions of the image. + /// + /// Path to the image file. + /// ImageDimensions. + ImageDimensions GetImageDimensions(string path); + + /// + /// Gets the dimensions of the image. + /// + /// The base item. + /// The information. + /// ImageDimensions. + ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info); + + /// + /// Gets the blurhash of the image. + /// + /// Path to the image file. + /// BlurHash. + string GetImageBlurHash(string path); + + /// + /// Gets the blurhash of the image. + /// + /// Path to the image file. + /// The image dimensions. + /// BlurHash. + string GetImageBlurHash(string path, ImageDimensions imageDimensions); + + /// + /// Gets the image cache tag. + /// + /// The items basePath. + /// The image last modification date. + /// Guid. + string? GetImageCacheTag(string baseItemPath, DateTime imageDateModified); + + /// + /// Gets the image cache tag. + /// + /// The item. + /// The image. + /// Guid. + string? GetImageCacheTag(BaseItemDto item, ChapterInfo image); + + /// + /// Gets the image cache tag. + /// + /// The item. + /// The image. + /// Guid. + string GetImageCacheTag(BaseItem item, ItemImageInfo image); + + /// + /// Gets the image cache tag. + /// + /// The item. + /// The image. + /// Guid. + string GetImageCacheTag(BaseItemDto item, ItemImageInfo image); + + string? GetImageCacheTag(BaseItem item, ChapterInfo chapter); + + string? GetImageCacheTag(User user); + + /// + /// Processes the image. + /// + /// The options. + /// Task. + Task<(string Path, string? MimeType, DateTime DateModified)> ProcessImage(ImageProcessingOptions options); + + /// + /// Gets the supported image output formats. + /// + /// . + IReadOnlyCollection GetSupportedImageOutputFormats(); + + /// + /// Creates the image collage. + /// + /// The options. + /// The library name to draw onto the collage. + void CreateImageCollage(ImageCollageOptions options, string? libraryName); + } +} diff --git a/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs b/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs new file mode 100644 index 00000000..e9c88ffb --- /dev/null +++ b/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs @@ -0,0 +1,35 @@ +#nullable disable + +using System.Collections.Generic; + +#pragma warning disable CS1591 + +namespace MediaBrowser.Controller.Drawing +{ + public class ImageCollageOptions + { + /// + /// Gets or sets the input paths. + /// + /// The input paths. + public IReadOnlyList InputPaths { get; set; } + + /// + /// Gets or sets the output path. + /// + /// The output path. + public string OutputPath { get; set; } + + /// + /// Gets or sets the width. + /// + /// The width. + public int Width { get; set; } + + /// + /// Gets or sets the height. + /// + /// The height. + public int Height { get; set; } + } +} diff --git a/MediaBrowser.Controller/Drawing/ImageHelper.cs b/MediaBrowser.Controller/Drawing/ImageHelper.cs new file mode 100644 index 00000000..9ef92bc9 --- /dev/null +++ b/MediaBrowser.Controller/Drawing/ImageHelper.cs @@ -0,0 +1,17 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Model.Drawing; + +namespace MediaBrowser.Controller.Drawing +{ + public static class ImageHelper + { + public static ImageDimensions GetNewImageSize(ImageProcessingOptions options, ImageDimensions originalImageSize) + { + // Determine the output size based on incoming parameters + var newSize = DrawingUtils.Resize(originalImageSize, options.Width ?? 0, options.Height ?? 0, options.MaxWidth ?? 0, options.MaxHeight ?? 0); + newSize = DrawingUtils.ResizeFill(newSize, options.FillWidth, options.FillHeight); + return newSize; + } + } +} diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs new file mode 100644 index 00000000..953cfe69 --- /dev/null +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -0,0 +1,126 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Drawing; + +namespace MediaBrowser.Controller.Drawing +{ + public class ImageProcessingOptions + { + public ImageProcessingOptions() + { + RequiresAutoOrientation = true; + } + + public Guid ItemId { get; set; } + + public BaseItem Item { get; set; } + + public ItemImageInfo Image { get; set; } + + public int ImageIndex { get; set; } + + public int? Width { get; set; } + + public int? Height { get; set; } + + public int? MaxWidth { get; set; } + + public int? MaxHeight { get; set; } + + public int? FillWidth { get; set; } + + public int? FillHeight { get; set; } + + public int Quality { get; set; } + + public IReadOnlyCollection SupportedOutputFormats { get; set; } + + public int? UnplayedCount { get; set; } + + public int? Blur { get; set; } + + public double PercentPlayed { get; set; } + + public string BackgroundColor { get; set; } + + public string ForegroundLayer { get; set; } + + public bool RequiresAutoOrientation { get; set; } + + private bool HasDefaultOptions(string originalImagePath) + { + return HasDefaultOptionsWithoutSize(originalImagePath) && + !Width.HasValue && + !Height.HasValue && + !MaxWidth.HasValue && + !MaxHeight.HasValue; + } + + public bool HasDefaultOptions(string originalImagePath, ImageDimensions? size) + { + if (!size.HasValue) + { + return HasDefaultOptions(originalImagePath); + } + + if (!HasDefaultOptionsWithoutSize(originalImagePath)) + { + return false; + } + + var sizeValue = size.Value; + + if (Width.HasValue && !sizeValue.Width.Equals(Width.Value)) + { + return false; + } + + if (Height.HasValue && !sizeValue.Height.Equals(Height.Value)) + { + return false; + } + + if (MaxWidth.HasValue && sizeValue.Width > MaxWidth.Value) + { + return false; + } + + if (MaxHeight.HasValue && sizeValue.Height > MaxHeight.Value) + { + return false; + } + + if (sizeValue.Width > FillWidth || sizeValue.Height > FillHeight) + { + return false; + } + + return true; + } + + private bool HasDefaultOptionsWithoutSize(string originalImagePath) + { + return (Quality >= 90) && + IsFormatSupported(originalImagePath) && + PercentPlayed.Equals(0) && + !UnplayedCount.HasValue && + !Blur.HasValue && + string.IsNullOrEmpty(BackgroundColor) && + string.IsNullOrEmpty(ForegroundLayer); + } + + private bool IsFormatSupported(string originalImagePath) + { + var ext = Path.GetExtension(originalImagePath); + ext = ext.Replace(".jpeg", ".jpg", StringComparison.OrdinalIgnoreCase); + return SupportedOutputFormats.Any(outputFormat => string.Equals(ext, outputFormat.GetExtension(), StringComparison.OrdinalIgnoreCase)); + } + } +} diff --git a/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs b/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs new file mode 100644 index 00000000..10326363 --- /dev/null +++ b/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs @@ -0,0 +1,27 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Controller.Drawing +{ + public static class ImageProcessorExtensions + { + public static string? GetImageCacheTag(this IImageProcessor processor, BaseItem item, ImageType imageType) + { + return processor.GetImageCacheTag(item, imageType, 0); + } + + public static string? GetImageCacheTag(this IImageProcessor processor, BaseItem item, ImageType imageType, int imageIndex) + { + var imageInfo = item.GetImageInfo(imageType, imageIndex); + + if (imageInfo is null) + { + return null; + } + + return processor.GetImageCacheTag(item, imageInfo); + } + } +} diff --git a/MediaBrowser.Controller/Dto/DtoOptions.cs b/MediaBrowser.Controller/Dto/DtoOptions.cs new file mode 100644 index 00000000..a71cdbd6 --- /dev/null +++ b/MediaBrowser.Controller/Dto/DtoOptions.cs @@ -0,0 +1,68 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; + +namespace MediaBrowser.Controller.Dto +{ + public class DtoOptions + { + private static readonly ItemFields[] DefaultExcludedFields = new[] + { + ItemFields.SeasonUserData, + ItemFields.RefreshState + }; + + private static readonly ImageType[] AllImageTypes = Enum.GetValues(); + + private static readonly ItemFields[] AllItemFields = Enum.GetValues() + .Except(DefaultExcludedFields) + .ToArray(); + + public DtoOptions() + : this(true) + { + } + + public DtoOptions(bool allFields) + { + ImageTypeLimit = int.MaxValue; + EnableImages = true; + EnableUserData = true; + AddCurrentProgram = true; + + Fields = allFields ? AllItemFields : []; + ImageTypes = AllImageTypes; + } + + public IReadOnlyList Fields { get; set; } + + public IReadOnlyList ImageTypes { get; set; } + + public int ImageTypeLimit { get; set; } + + public bool EnableImages { get; set; } + + public bool AddProgramRecordingInfo { get; set; } + + public bool EnableUserData { get; set; } + + public bool AddCurrentProgram { get; set; } + + public bool ContainsField(ItemFields field) + => Fields.Contains(field); + + public int GetImageLimit(ImageType type) + { + if (EnableImages && ImageTypes.Contains(type)) + { + return ImageTypeLimit; + } + + return 0; + } + } +} diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs new file mode 100644 index 00000000..f1d507fc --- /dev/null +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -0,0 +1,52 @@ +#pragma warning disable CA1002 + +using System.Collections.Generic; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Dto; + +namespace MediaBrowser.Controller.Dto +{ + /// + /// Interface IDtoService. + /// + public interface IDtoService + { + /// + /// Gets the primary image aspect ratio. + /// + /// The item. + /// System.Nullable<System.Double>. + double? GetPrimaryImageAspectRatio(BaseItem item); + + /// + /// Gets the base item dto. + /// + /// The item. + /// The options. + /// The user. + /// The owner. + /// BaseItemDto. + BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User? user = null, BaseItem? owner = null); + + /// + /// Gets the base item dtos. + /// + /// The items. + /// The options. + /// The user. + /// The owner. + /// The of . + IReadOnlyList GetBaseItemDtos(IReadOnlyList items, DtoOptions options, User? user = null, BaseItem? owner = null); + + /// + /// Gets the item by name dto. + /// + /// The item. + /// The dto options. + /// The list of tagged items. + /// The user. + /// The item dto. + BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List? taggedItems, User? user = null); + } +} diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs new file mode 100644 index 00000000..a02802f4 --- /dev/null +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -0,0 +1,204 @@ +#nullable disable + +#pragma warning disable CA1819, CS1591 + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Extensions; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.IO; + +namespace MediaBrowser.Controller.Entities +{ + /// + /// Specialized folder that can have items added to it's children by external entities. + /// Used for our RootFolder so plugins can add items. + /// + public class AggregateFolder : Folder + { + private readonly Lock _childIdsLock = new(); + + /// + /// The _virtual children. + /// + private readonly ConcurrentBag _virtualChildren = new ConcurrentBag(); + private bool _requiresRefresh; + private Guid[] _childrenIds = null; + + public AggregateFolder() + { + PhysicalLocationsList = Array.Empty(); + } + + /// + /// Gets the virtual children. + /// + /// The virtual children. + public ConcurrentBag VirtualChildren => _virtualChildren; + + [JsonIgnore] + public override bool IsPhysicalRoot => true; + + [JsonIgnore] + public override bool SupportsPlayedStatus => false; + + [JsonIgnore] + public override string[] PhysicalLocations => PhysicalLocationsList; + + public string[] PhysicalLocationsList { get; set; } + + public override bool CanDelete() + { + return false; + } + + protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService) + { + return CreateResolveArgs(directoryService, true).FileSystemChildren; + } + + protected override IReadOnlyList LoadChildren() + { + lock (_childIdsLock) + { + if (_childrenIds is null || _childrenIds.Length == 0) + { + var list = base.LoadChildren(); + _childrenIds = list.Select(i => i.Id).ToArray(); + return list; + } + + return _childrenIds.Select(LibraryManager.GetItemById).Where(i => i is not null).ToList(); + } + } + + private void ClearCache() + { + lock (_childIdsLock) + { + _childrenIds = null; + } + } + + public override bool RequiresRefresh() + { + var changed = base.RequiresRefresh() || _requiresRefresh; + + if (!changed) + { + var locations = PhysicalLocations; + + var newLocations = CreateResolveArgs(new DirectoryService(FileSystem), false).PhysicalLocations; + + if (!locations.SequenceEqual(newLocations)) + { + changed = true; + } + } + + return changed; + } + + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) + { + ClearCache(); + + var changed = base.BeforeMetadataRefresh(replaceAllMetadata) || _requiresRefresh; + _requiresRefresh = false; + return changed; + } + + private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations) + { + ClearCache(); + + var path = ContainingFolderPath; + + var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager) + { + FileInfo = FileSystem.GetDirectoryInfo(path) + }; + + // Gather child folder and files + if (args.IsDirectory) + { + // When resolving the root, we need it's grandchildren (children of user views) + var flattenFolderDepth = 2; + + var files = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, FileSystem, CollectionFolder.ApplicationHost, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: true); + + // Need to remove subpaths that may have been resolved from shortcuts + // Example: if \\server\movies exists, then strip out \\server\movies\action + files = LibraryManager.NormalizeRootPathList(files).ToArray(); + + args.FileSystemChildren = files; + } + + _requiresRefresh = _requiresRefresh || !args.PhysicalLocations.SequenceEqual(PhysicalLocations); + if (setPhysicalLocations) + { + PhysicalLocationsList = args.PhysicalLocations; + } + + return args; + } + + protected override IEnumerable GetNonCachedChildren(IDirectoryService directoryService) + { + return base.GetNonCachedChildren(directoryService).Concat(_virtualChildren); + } + + protected override async Task ValidateChildrenInternal(IProgress progress, bool recursive, bool refreshChildMetadata, bool allowRemoveRoot, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService, CancellationToken cancellationToken) + { + ClearCache(); + + await base.ValidateChildrenInternal(progress, recursive, refreshChildMetadata, allowRemoveRoot, refreshOptions, directoryService, cancellationToken) + .ConfigureAwait(false); + + ClearCache(); + } + + /// + /// Adds the virtual child. + /// + /// The child. + /// Throws if child is null. + public void AddVirtualChild(BaseItem child) + { + ArgumentNullException.ThrowIfNull(child); + + _virtualChildren.Add(child); + } + + /// + /// Finds the virtual child. + /// + /// The id. + /// BaseItem. + /// The id is empty. + public BaseItem FindVirtualChild(Guid id) + { + if (id.IsEmpty()) + { + throw new ArgumentNullException(nameof(id)); + } + + foreach (var child in _virtualChildren) + { + if (child.Id.Equals(id)) + { + return child; + } + } + + return null; + } + } +} diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs new file mode 100644 index 00000000..709d4b70 --- /dev/null +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -0,0 +1,153 @@ +#nullable disable + +#pragma warning disable CA1002, CA1724, CA1826, CS1591 + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dto; + +namespace MediaBrowser.Controller.Entities.Audio +{ + /// + /// Class Audio. + /// + public class Audio : BaseItem, + IHasAlbumArtist, + IHasArtist, + IHasMusicGenres, + IHasLookupInfo, + IHasMediaSources + { + public Audio() + { + Artists = Array.Empty(); + AlbumArtists = Array.Empty(); + LyricFiles = Array.Empty(); + } + + /// + [JsonIgnore] + public IReadOnlyList Artists { get; set; } + + /// + [JsonIgnore] + public IReadOnlyList AlbumArtists { get; set; } + + [JsonIgnore] + public override bool SupportsPlayedStatus => true; + + [JsonIgnore] + public override bool SupportsPeople => true; + + [JsonIgnore] + public override bool SupportsAddingToPlaylist => true; + + [JsonIgnore] + public override bool SupportsInheritedParentImages => true; + + [JsonIgnore] + protected override bool SupportsOwnedItems => false; + + [JsonIgnore] + public override Folder LatestItemsIndexContainer => AlbumEntity; + + [JsonIgnore] + public MusicAlbum AlbumEntity => FindParent(); + + /// + /// Gets the type of the media. + /// + /// The type of the media. + [JsonIgnore] + public override MediaType MediaType => MediaType.Audio; + + /// + /// Gets or sets a value indicating whether this audio has lyrics. + /// + public bool? HasLyrics { get; set; } + + /// + /// Gets or sets the list of lyric paths. + /// + public IReadOnlyList LyricFiles { get; set; } + + public override double GetDefaultPrimaryImageAspectRatio() + { + return 1; + } + + public override bool CanDownload() + { + return IsFileProtocol; + } + + /// + /// Creates the name of the sort. + /// + /// System.String. + protected override string CreateSortName() + { + return (ParentIndexNumber is not null ? ParentIndexNumber.Value.ToString("0000 - ", CultureInfo.InvariantCulture) : string.Empty) + + (IndexNumber is not null ? IndexNumber.Value.ToString("0000 - ", CultureInfo.InvariantCulture) : string.Empty) + Name; + } + + public override List GetUserDataKeys() + { + var list = base.GetUserDataKeys(); + + var songKey = IndexNumber.HasValue ? IndexNumber.Value.ToString("0000", CultureInfo.InvariantCulture) : string.Empty; + + if (ParentIndexNumber.HasValue) + { + songKey = ParentIndexNumber.Value.ToString("0000", CultureInfo.InvariantCulture) + "-" + songKey; + } + + songKey += Name; + + if (!string.IsNullOrEmpty(Album)) + { + songKey = Album + "-" + songKey; + } + + var albumArtist = AlbumArtists.FirstOrDefault(); + if (!string.IsNullOrEmpty(albumArtist)) + { + songKey = albumArtist + "-" + songKey; + } + + list.Insert(0, songKey); + + return list; + } + + public override UnratedItem GetBlockUnratedType() + { + if (SourceType == SourceType.Library) + { + return UnratedItem.Music; + } + + return base.GetBlockUnratedType(); + } + + public SongInfo GetLookupInfo() + { + var info = GetItemLookupInfo(); + + info.AlbumArtists = AlbumArtists; + info.Album = Album; + info.Artists = Artists; + + return info; + } + + protected override IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources() + => new[] { ((BaseItem)this, MediaSourceType.Default) }; + } +} diff --git a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs new file mode 100644 index 00000000..b085398c --- /dev/null +++ b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs @@ -0,0 +1,33 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Controller.Library; + +namespace MediaBrowser.Controller.Entities.Audio +{ + public interface IHasAlbumArtist + { + IReadOnlyList AlbumArtists { get; set; } + } + + public interface IHasArtist + { + /// + /// Gets or sets the artists. + /// + /// The artists. + IReadOnlyList Artists { get; set; } + } + + public static class Extensions + { + public static IEnumerable GetAllArtists(this T item) + where T : IHasArtist, IHasAlbumArtist + { + return item.AlbumArtists.Concat(item.Artists).DistinctNames(); + } + } +} diff --git a/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs b/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs new file mode 100644 index 00000000..c2dae5a2 --- /dev/null +++ b/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs @@ -0,0 +1,11 @@ +#nullable disable + +#pragma warning disable CA1819, CS1591 + +namespace MediaBrowser.Controller.Entities.Audio +{ + public interface IHasMusicGenres + { + string[] Genres { get; } + } +} diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs new file mode 100644 index 00000000..d016d8f6 --- /dev/null +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -0,0 +1,227 @@ +#nullable disable + +#pragma warning disable CA1721, CA1826, CS1591 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider; + +namespace MediaBrowser.Controller.Entities.Audio +{ + /// + /// Class MusicAlbum. + /// + [Common.RequiresSourceSerialisation] + public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo, IMetadataContainer + { + public MusicAlbum() + { + Artists = Array.Empty(); + AlbumArtists = Array.Empty(); + } + + /// + public IReadOnlyList AlbumArtists { get; set; } + + /// + public IReadOnlyList Artists { get; set; } + + [JsonIgnore] + public override bool SupportsAddingToPlaylist => true; + + [JsonIgnore] + public override bool SupportsInheritedParentImages => true; + + [JsonIgnore] + public MusicArtist MusicArtist => GetMusicArtist(new DtoOptions(true)); + + [JsonIgnore] + public override bool SupportsPlayedStatus => false; + + [JsonIgnore] + public override bool SupportsCumulativeRunTimeTicks => true; + + [JsonIgnore] + public string AlbumArtist => AlbumArtists.FirstOrDefault(); + + [JsonIgnore] + public override bool SupportsPeople => true; + + /// + /// Gets the tracks. + /// + /// The tracks. + [JsonIgnore] + public IEnumerable